chore(go): audit providers claudecode codex cursor gemini

Add managed_runtime.go (paths, config validation, identity, runtime file
changes, status checks) and shell_command.go (quoting, PowerShell
encoding, command parsing) and move the four providers onto them.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
雷电芽衣 committed 2026-09-03 01:42:27 -04:00
1 parent c1caed3d42
commit a7fc75bf6b
39 files changed
+914 -2018

No files matched your search

+1 -1
View File
@@ -211,7 +211,7 @@ func resolveAugmentWrapperPaths(agentDirectory string) augmentWrapperPaths {
} }
func requireAugmentAbsoluteNode(nodePath string) error { func requireAugmentAbsoluteNode(nodePath string) error {
if !filepath.IsAbs(nodePath) || !isClaudeNodeExecutable(nodePath) { if !filepath.IsAbs(nodePath) || !isNodeExecutable(nodePath) {
return fmt.Errorf("Auggie hooks require an absolute Node.js executable path") return fmt.Errorf("Auggie hooks require an absolute Node.js executable path")
} }
return nil return nil
+5 -4
View File
@@ -10,8 +10,7 @@ type ClaudeCodePlugin struct {
rename renameFunc rename renameFunc
} }
// ManagesGuardRuntime reports that Claude Code commits its provider guard with // ManagesGuardRuntime reports that the guard is committed with the audit runtime.
// the audit runtime and user settings document.
func (p *ClaudeCodePlugin) ManagesGuardRuntime() bool { func (p *ClaudeCodePlugin) ManagesGuardRuntime() bool {
return true return true
} }
@@ -73,13 +72,14 @@ func (p *ClaudeCodePlugin) Install(config InstallConfig) error {
if err != nil { if err != nil {
return err return err
} }
if err := writeClaudeChanges( if err := writeManagedChanges(
changes, changes,
"Install Claude Code hooks", "Install Claude Code hooks",
p.rename, p.rename,
paths.runtimeRoot, paths.runtimeRoot,
paths.agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Claude Code configuration directory",
); err != nil { ); err != nil {
return err return err
} }
@@ -105,13 +105,14 @@ func (p *ClaudeCodePlugin) Uninstall(agentID string) error {
if err != nil { if err != nil {
return err return err
} }
return writeClaudeChanges( return writeManagedChanges(
[]*fileChange{change}, []*fileChange{change},
"Uninstall Claude Code hooks", "Uninstall Claude Code hooks",
p.rename, p.rename,
"", "",
"", "",
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Claude Code configuration directory",
) )
} }
+1 -5
View File
@@ -77,11 +77,7 @@ type claudeRenderedDocument struct {
remove bool remove bool
} }
type claudeRuntimeContract struct { type claudeRuntimeContract = managedRuntimeContract
agentID string
guardPath string
auditPath string
}
func cloneClaudeObject(value map[string]any) map[string]any { func cloneClaudeObject(value map[string]any) map[string]any {
clone := make(map[string]any, len(value)) clone := make(map[string]any, len(value))
+19 -153
View File
@@ -1,182 +1,48 @@
package plugins package plugins
import ( import "fmt"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type claudeRuntimePaths struct {
runtimeRoot string
agentDirectory string
configPath string
keyPath string
guardPath string
auditPath string
}
func validateClaudeInstallConfig(config InstallConfig) error {
for _, field := range []struct{ name, value string }{
{"agent name", config.AgentName},
{"organization ID", config.OrgID},
{"agent ID", config.AgentID},
{"key ID", config.KID},
{"private key", config.PrivateKey},
{"base URL", config.BaseURL},
{"guard script path", config.GuardScriptPath},
} {
if field.value == "" {
return fmt.Errorf("%s is required", field.name)
}
}
if config.AgentName != claudeAgentKey {
return fmt.Errorf(
"Claude Code installation requires agent name %s",
claudeAgentKey,
)
}
if strings.TrimSpace(config.OrgID) == "" {
return fmt.Errorf("organization ID is required")
}
if strings.TrimSpace(config.KID) == "" {
return fmt.Errorf("key ID is required")
}
if config.Token != "" && strings.TrimSpace(config.Token) == "" {
return fmt.Errorf("token must contain a non-whitespace value when provided")
}
if err := validateManagedPrivateKey(config.PrivateKey); err != nil {
return err
}
return validateManagedBaseURL(config.BaseURL)
}
func claudeAgentPaths(config InstallConfig) (*claudeRuntimePaths, error) {
if err := validateClaudeInstallConfig(config); err != nil {
return nil, err
}
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return nil, err
}
paths := &claudeRuntimePaths{
runtimeRoot: runtimeRoot, agentDirectory: agentDirectory,
configPath: filepath.Join(agentDirectory, "config.json"),
keyPath: filepath.Join(agentDirectory, "private.key"),
guardPath: filepath.Join(agentDirectory, claudeGuardScript),
auditPath: filepath.Join(agentDirectory, claudeAuditScript),
}
if !filepath.IsAbs(config.GuardScriptPath) ||
!sameClaudePath(config.GuardScriptPath, paths.guardPath) {
return nil, fmt.Errorf(
"guard runtime must use the managed Elydora agent directory: %s",
paths.guardPath,
)
}
if config.HookScript != "" && (!filepath.IsAbs(config.HookScript) ||
!sameClaudePath(config.HookScript, paths.auditPath)) {
return nil, fmt.Errorf(
"audit runtime must use the managed Elydora agent directory: %s",
paths.auditPath,
)
}
return paths, nil
}
func preflightClaudeInstallation( func preflightClaudeInstallation(
config InstallConfig, config InstallConfig,
document *claudeDocument, document *claudeDocument,
) (*claudeRuntimePaths, string, error) { ) (*managedRuntimePaths, string, error) {
if document == nil || document.filePath == "" { if document == nil || document.filePath == "" {
return nil, "", fmt.Errorf( return nil, "", fmt.Errorf("Claude Code installation requires a user settings document")
"Claude Code installation requires a user settings document",
)
} }
paths, err := claudeAgentPaths(config) if err := validateManagedInstallConfig(config, claudeAgentKey, "Claude Code"); err != nil {
return nil, "", err
}
paths, err := resolveManagedRuntimePaths(config, claudeGuardScript, claudeAuditScript)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if err := validateClaudeRuntimeIdentity(paths.agentDirectory, config.AgentID); err != nil { if err := validateManagedRuntimeIdentity(
paths.agentDirectory, config.AgentID, claudeAgentKey, "Claude Code",
); err != nil {
return nil, "", err return nil, "", err
} }
nodePath, err := resolveNodeRuntime() nodePath, err := resolveAbsoluteNodeRuntime("Claude Code")
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if err := requireClaudeAbsoluteNode(nodePath); err != nil {
return nil, "", err
}
return paths, nodePath, nil return paths, nodePath, nil
} }
func buildClaudeRuntimeConfig(config InstallConfig) ([]byte, error) {
value := map[string]any{
"org_id": config.OrgID, "agent_id": config.AgentID, "kid": config.KID,
"base_url": config.BaseURL, "agent_name": claudeAgentKey,
}
if config.Token != "" {
value["token"] = config.Token
}
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
}
encoded = append(encoded, '\n')
if len(encoded) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
return encoded, nil
}
func prepareClaudeInstallationChanges( func prepareClaudeInstallationChanges(
config InstallConfig, config InstallConfig,
paths *claudeRuntimePaths, paths *managedRuntimePaths,
rendered *claudeRenderedDocument, rendered *claudeRenderedDocument,
) ([]*fileChange, error) { ) ([]*fileChange, error) {
runtimeConfig, err := buildClaudeRuntimeConfig(config) changes, err := managedRuntimeFileChanges(
config,
paths,
claudeAgentKey,
generateGuardScript(claudeAgentKey, config.AgentID, "", false, ""),
buildHookScriptWithOutput(claudeAgentKey, config.AgentID, "", false, true),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{
paths.guardPath, "Elydora guard runtime",
[]byte(generateGuardScript(claudeAgentKey, config.AgentID, "", false, "")),
0700,
},
{paths.configPath, "Elydora runtime config", runtimeConfig, 0600},
{paths.keyPath, "Elydora private key", []byte(config.PrivateKey), 0600},
{
paths.auditPath, "Elydora audit runtime",
[]byte(buildHookScriptWithOutput(
claudeAgentKey,
config.AgentID,
"",
false,
true,
)),
0700,
},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
change, err := prepareFileChange(item.path, item.label, item.content, item.mode)
if err != nil {
return nil, err
}
changes = append(changes, change)
}
documentChange, err := prepareRenderedClaudeChange(rendered) documentChange, err := prepareRenderedClaudeChange(rendered)
if err != nil { if err != nil {
return nil, err return nil, err
-35
View File
@@ -71,38 +71,3 @@ func prepareRenderedClaudeChange(
rendered.remove, rendered.remove,
) )
} }
func writeClaudeChanges(
changes []*fileChange,
label string,
rename renameFunc,
runtimeRoot string,
agentDirectory string,
settingsDirectory string,
) error {
hasChanges := false
for _, change := range changes {
if change != nil {
hasChanges = true
break
}
}
if !hasChanges {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
if err := ensureManagedDirectory(
settingsDirectory,
"Claude Code configuration directory",
); err != nil {
return err
}
return writeChanges(changes, label, rename)
}
+6 -75
View File
@@ -1,73 +1,18 @@
package plugins package plugins
import ( import (
"fmt"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strings" "strings"
) )
type claudeRuntimeReference struct { type claudeRuntimeReference = managedScriptReference
agentID string
scriptPath string
}
func sameClaudePath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if absolute, err := filepath.Abs(left); err == nil {
left = absolute
}
if absolute, err := filepath.Abs(right); err == nil {
right = absolute
}
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameClaudeAgentID(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func isClaudeNodeExecutable(path string) bool {
name := filepath.Base(path)
return name == "node" || strings.EqualFold(name, "node.exe")
}
func sameClaudeFileName(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func claudeRuntimeReferenceForScript( func claudeRuntimeReferenceForScript(
scriptPath string, scriptPath string,
scriptName string, scriptName string,
) (*claudeRuntimeReference, error) { ) (*claudeRuntimeReference, error) {
if !filepath.IsAbs(scriptPath) || return resolveManagedScript(scriptPath, scriptName)
!sameClaudeFileName(filepath.Base(scriptPath), scriptName) {
return nil, nil
}
agentDirectory := filepath.Dir(scriptPath)
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
if !sameClaudePath(filepath.Dir(agentDirectory), runtimeRoot) {
return nil, nil
}
agentID := filepath.Base(agentDirectory)
if agentID == "" || agentID == "." || agentID == ".." {
return nil, nil
}
return &claudeRuntimeReference{agentID: agentID, scriptPath: scriptPath}, nil
} }
func claudeLegacyReference( func claudeLegacyReference(
@@ -95,7 +40,7 @@ func managedClaudeReference(
handler["statusMessage"] == statusMessage { handler["statusMessage"] == statusMessage {
nodePath, nodeOK := handler["command"].(string) nodePath, nodeOK := handler["command"].(string)
args, argsOK := handler["args"].([]any) args, argsOK := handler["args"].([]any)
if nodeOK && filepath.IsAbs(nodePath) && isClaudeNodeExecutable(nodePath) && if nodeOK && filepath.IsAbs(nodePath) && isNodeExecutable(nodePath) &&
argsOK && len(args) == 1 { argsOK && len(args) == 1 {
if scriptPath, ok := args[0].(string); ok { if scriptPath, ok := args[0].(string); ok {
return claudeRuntimeReferenceForScript(scriptPath, scriptName) return claudeRuntimeReferenceForScript(scriptPath, scriptName)
@@ -146,7 +91,7 @@ func removeManagedClaudeGroups(
return nil, false, err return nil, false, err
} }
owned := reference != nil && owned := reference != nil &&
(agentID == "" || sameClaudeAgentID(reference.agentID, agentID)) (agentID == "" || sameManagedAgentID(reference.agentID, agentID))
if owned { if owned {
removed = true removed = true
continue continue
@@ -235,13 +180,6 @@ func entirelyManagedClaudeDocument(document *claudeDocument) bool {
return handlerCount > 0 return handlerCount > 0
} }
func claudeReferenceKey(agentID string) string {
if runtime.GOOS == "windows" {
return strings.ToLower(agentID)
}
return agentID
}
func claudeReferencesForEvent( func claudeReferencesForEvent(
groups []claudeGroup, groups []claudeGroup,
scriptName string, scriptName string,
@@ -263,7 +201,7 @@ func claudeReferencesForEvent(
return nil, err return nil, err
} }
if reference != nil { if reference != nil {
key := claudeReferenceKey(reference.agentID) key := managedReferenceKey(reference.agentID)
result[key] = append(result[key], *reference) result[key] = append(result[key], *reference)
} }
} }
@@ -307,7 +245,7 @@ func claudeRuntimeContracts(hooks claudeHooks) ([]claudeRuntimeContract, error)
success := successes[key] success := successes[key]
failure := failures[key] failure := failures[key]
if len(guard) != 1 || len(success) != 1 || len(failure) != 1 || if len(guard) != 1 || len(success) != 1 || len(failure) != 1 ||
!sameClaudePath(success[0].scriptPath, failure[0].scriptPath) { !sameManagedPath(success[0].scriptPath, failure[0].scriptPath) {
continue continue
} }
contracts = append(contracts, claudeRuntimeContract{ contracts = append(contracts, claudeRuntimeContract{
@@ -317,10 +255,3 @@ func claudeRuntimeContracts(hooks claudeHooks) ([]claudeRuntimeContract, error)
} }
return contracts, nil return contracts, nil
} }
func requireClaudeAbsoluteNode(nodePath string) error {
if !filepath.IsAbs(nodePath) || !isClaudeNodeExecutable(nodePath) {
return fmt.Errorf("Claude Code hooks require an absolute Node.js executable path")
}
return nil
}
+2 -228
View File
@@ -1,231 +1,5 @@
package plugins package plugins
import ( func claudeRuntimeFilesExist(contracts []managedRuntimeContract) (bool, error) {
"fmt" return managedRuntimeFilesExist(contracts, claudeAgentKey, "Claude Code", nil)
"path/filepath"
"sort"
"strings"
)
func readClaudeRuntimeConfig(path string) (map[string]any, bool, error) {
snapshot, err := readManagedFile(path, "Elydora runtime config", maxRuntimeConfigBytes)
if err != nil || snapshot == nil {
return nil, snapshot != nil, err
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
config, err := decodeStrictJSONObject(snapshot.contents, label)
return config, true, err
}
func requireClaudeRuntimeString(
config map[string]any,
field string,
configPath string,
) (string, error) {
value, ok := config[field].(string)
if !ok || strings.TrimSpace(value) == "" {
return "", fmt.Errorf(
"elydora runtime config %s is invalid: %s",
field,
configPath,
)
}
return value, nil
}
func validateClaudeRuntimeConfig(
config map[string]any,
expectedAgentID string,
configPath string,
) error {
supported := stringSet(
"org_id", "agent_id", "kid", "base_url", "token", "agent_name",
)
fields := make([]string, 0, len(config))
for field := range config {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
if _, ok := supported[field]; !ok {
return fmt.Errorf(
`elydora runtime config has unsupported field %q: %s`,
field,
configPath,
)
}
}
if _, err := requireClaudeRuntimeString(config, "org_id", configPath); err != nil {
return err
}
if _, err := requireClaudeRuntimeString(config, "kid", configPath); err != nil {
return err
}
agentID, err := requireClaudeRuntimeString(config, "agent_id", configPath)
if err != nil {
return err
}
if !sameClaudeAgentID(agentID, expectedAgentID) ||
config["agent_name"] != claudeAgentKey {
return fmt.Errorf(
"elydora runtime identity does not match Claude Code hooks: %s",
configPath,
)
}
if _, exists := config["token"]; exists {
if _, err := requireClaudeRuntimeString(config, "token", configPath); err != nil {
return err
}
}
baseURL, err := requireClaudeRuntimeString(config, "base_url", configPath)
if err != nil {
return err
}
if err := validateManagedBaseURL(baseURL); err != nil {
return fmt.Errorf(
"elydora runtime config base URL is invalid at %s: %w",
configPath,
err,
)
}
return nil
}
func validateClaudeRuntimeIdentity(agentDirectory, agentID string) error {
runtimeRoot := filepath.Dir(agentDirectory)
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return err
}
configPath := filepath.Join(agentDirectory, "config.json")
config, configExists, err := readClaudeRuntimeConfig(configPath)
if err != nil {
return err
}
artifactExists := false
for _, item := range []struct {
path string
label string
limit int64
}{
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
{filepath.Join(agentDirectory, claudeGuardScript), "Elydora guard runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, claudeAuditScript), "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "chain-state.json"), "Elydora chain state", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "status-cache.json"), "Elydora status cache", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "error.log"), "Elydora error log", maxManagedSourceBytes},
} {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil {
return inspectErr
}
artifactExists = artifactExists || exists
}
if !configExists {
if artifactExists {
return fmt.Errorf(
"elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != claudeAgentKey ||
!sameClaudeAgentID(configuredID, agentID) {
return fmt.Errorf(
"elydora runtime config identity does not match Claude Code agent %s: %s",
agentID,
configPath,
)
}
return nil
}
func claudeRuntimeContractExists(contract claudeRuntimeContract) (bool, error) {
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return false, err
}
agentDirectory := filepath.Dir(contract.guardPath)
if !sameClaudePath(filepath.Dir(agentDirectory), runtimeRoot) ||
!sameClaudePath(
contract.auditPath,
filepath.Join(agentDirectory, claudeAuditScript),
) {
return false, nil
}
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return false, err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return false, err
}
configPath := filepath.Join(agentDirectory, "config.json")
keyPath := filepath.Join(agentDirectory, "private.key")
config, configExists, err := readClaudeRuntimeConfig(configPath)
if err != nil {
return false, err
}
key, err := readManagedFile(keyPath, "Elydora private key", maxProtectedSecretBytes)
if err != nil {
return false, err
}
guard, err := readManagedFile(
contract.guardPath,
"Elydora guard runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
audit, err := readManagedFile(
contract.auditPath,
"Elydora audit runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
if !configExists || key == nil || guard == nil || audit == nil {
return false, nil
}
if err := validateClaudeRuntimeConfig(config, contract.agentID, configPath); err != nil {
return false, err
}
if err := validateManagedPrivateKey(string(key.contents)); err != nil {
return false, fmt.Errorf("elydora private key at %s: %w", keyPath, err)
}
return len(guard.contents) > 0 && len(audit.contents) > 0, nil
}
func claudeRuntimeFilesExist(contracts []claudeRuntimeContract) (bool, error) {
for _, contract := range contracts {
exists, err := claudeRuntimeContractExists(contract)
if err != nil {
return false, err
}
if exists {
return true, nil
}
}
return false, nil
} }
@@ -60,7 +60,7 @@ func TestClaudeInstallRollsBackAllRuntimesAfterSettingsFailure(t *testing.T) {
} }
before := snapshotClaudeFiles(t, paths...) before := snapshotClaudeFiles(t, paths...)
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if sameClaudePath(destination, fixture.configPath) && if sameManagedPath(destination, fixture.configPath) &&
strings.HasSuffix(source, ".tmp") { strings.HasSuffix(source, ".tmp") {
return errors.New("injected Claude settings failure") return errors.New("injected Claude settings failure")
} }
@@ -114,13 +114,14 @@ func TestPreparedClaudeInstallRejectsConcurrentSettingsChange(t *testing.T) {
if err := os.WriteFile(fixture.configPath, concurrent, 0600); err != nil { if err := os.WriteFile(fixture.configPath, concurrent, 0600); err != nil {
t.Fatalf("write concurrent settings: %v", err) t.Fatalf("write concurrent settings: %v", err)
} }
err = writeClaudeChanges( err = writeManagedChanges(
changes, changes,
"Install Claude Code hooks", "Install Claude Code hooks",
nil, nil,
paths.runtimeRoot, paths.runtimeRoot,
paths.agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Claude Code configuration directory",
) )
if err == nil || !strings.Contains(err.Error(), "changed during installation") { if err == nil || !strings.Contains(err.Error(), "changed during installation") {
t.Fatalf("prepared install error = %v", err) t.Fatalf("prepared install error = %v", err)
@@ -176,7 +177,7 @@ func TestClaudeUninstallPreservesSettingsAfterCommitFailure(t *testing.T) {
t.Fatalf("read installed settings: %v", err) t.Fatalf("read installed settings: %v", err)
} }
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if sameClaudePath(destination, fixture.configPath) && if sameManagedPath(destination, fixture.configPath) &&
strings.HasSuffix(source, ".tmp") { strings.HasSuffix(source, ".tmp") {
return errors.New("injected Claude uninstall failure") return errors.New("injected Claude uninstall failure")
} }
+1 -1
View File
@@ -109,7 +109,7 @@ func preflightClineInstallation(
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !filepath.IsAbs(nodePath) || !isClaudeNodeExecutable(nodePath) { if !filepath.IsAbs(nodePath) || !isNodeExecutable(nodePath) {
return nil, fmt.Errorf("Cline hooks require an absolute Node.js executable path") return nil, fmt.Errorf("Cline hooks require an absolute Node.js executable path")
} }
return paths, nil return paths, nil
+6 -6
View File
@@ -10,14 +10,12 @@ type CodexPlugin struct {
rename renameFunc rename renameFunc
} }
// ManagesGuardRuntime reports that Codex commits its provider guard with the // ManagesGuardRuntime reports that the guard is committed with the audit runtime.
// audit runtime and user hook document.
func (p *CodexPlugin) ManagesGuardRuntime() bool { func (p *CodexPlugin) ManagesGuardRuntime() bool {
return true return true
} }
// PreflightInstall validates every existing source before the CLI creates // PreflightInstall validates every source before the CLI creates runtime state.
// runtime state.
func (p *CodexPlugin) PreflightInstall(config InstallConfig) error { func (p *CodexPlugin) PreflightInstall(config InstallConfig) error {
document, err := readCodexDocument() document, err := readCodexDocument()
if err != nil { if err != nil {
@@ -61,13 +59,14 @@ func (p *CodexPlugin) Install(config InstallConfig) error {
if err != nil { if err != nil {
return err return err
} }
if err := writeCodexChanges( if err := writeManagedChanges(
changes, changes,
"Install Codex hooks", "Install Codex hooks",
p.rename, p.rename,
paths.runtimeRoot, paths.runtimeRoot,
paths.agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Codex hooks directory",
); err != nil { ); err != nil {
return err return err
} }
@@ -90,13 +89,14 @@ func (p *CodexPlugin) Uninstall(agentID string) error {
if err != nil { if err != nil {
return err return err
} }
return writeCodexChanges( return writeManagedChanges(
[]*fileChange{change}, []*fileChange{change},
"Uninstall Codex hooks", "Uninstall Codex hooks",
p.rename, p.rename,
"", "",
"", "",
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Codex hooks directory",
) )
} }
+16 -176
View File
@@ -1,53 +1,12 @@
package plugins package plugins
import ( import "path/filepath"
"encoding/base64"
"encoding/binary"
"os"
"path/filepath"
"runtime"
"strings"
"unicode/utf16"
)
func quoteCodexPowerShell(value string) string {
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
}
func codexPowerShellPath() string {
root := "C:\\Windows"
if runtime.GOOS == "windows" {
configured := os.Getenv("SystemRoot")
if filepath.IsAbs(configured) && !strings.ContainsAny(configured, "\"%\r\n") {
root = configured
}
}
return strings.TrimRight(root, `\/`) +
`\System32\WindowsPowerShell\v1.0\powershell.exe`
}
func encodeCodexPowerShell(source string) string {
runes := utf16.Encode([]rune(source))
raw := make([]byte, len(runes)*2)
for index, value := range runes {
binary.LittleEndian.PutUint16(raw[index*2:], value)
}
return base64.StdEncoding.EncodeToString(raw)
}
func codexWindowsCommand(runtimePath, scriptPath string) string {
source := "& " + quoteCodexPowerShell(runtimePath) + " " +
quoteCodexPowerShell(scriptPath) + "; exit $LASTEXITCODE"
return `"` + codexPowerShellPath() +
`" -NoLogo -NoProfile -NonInteractive -EncodedCommand ` +
encodeCodexPowerShell(source)
}
func codexHandler(runtimePath, scriptPath, statusMessage string) map[string]any { func codexHandler(runtimePath, scriptPath, statusMessage string) map[string]any {
return map[string]any{ return map[string]any{
"type": "command", "type": "command",
"command": quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), "command": posixSource(runtimePath, scriptPath),
"commandWindows": codexWindowsCommand(runtimePath, scriptPath), "commandWindows": encodedWindowsCommand(runtimePath, scriptPath),
"timeout": codexHookTimeout, "timeout": codexHookTimeout,
"statusMessage": statusMessage, "statusMessage": statusMessage,
} }
@@ -57,124 +16,6 @@ func codexMatcherGroup(handler map[string]any) map[string]any {
return map[string]any{"matcher": "*", "hooks": []any{handler}} return map[string]any{"matcher": "*", "hooks": []any{handler}}
} }
func readCodexPOSIXArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); {
if strings.HasPrefix(command[index:], codexPOSIXApostrophe) {
value.WriteByte('\'')
index += len(codexPOSIXApostrophe)
continue
}
if command[index] == '\'' {
return value.String(), index + 1, true
}
value.WriteByte(command[index])
index++
}
return "", start, false
}
func parseCodexPOSIXCommand(value any) (string, string, bool) {
command, ok := value.(string)
if !ok {
return "", "", false
}
runtimePath, next, ok := readCodexPOSIXArgument(command, 0)
if !ok || next >= len(command) || command[next] != ' ' {
return "", "", false
}
scriptPath, end, ok := readCodexPOSIXArgument(command, next+1)
return runtimePath, scriptPath, ok && end == len(command)
}
func readCodexPowerShellArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); index++ {
if command[index] != '\'' {
value.WriteByte(command[index])
continue
}
if index+1 < len(command) && command[index+1] == '\'' {
value.WriteByte('\'')
index++
continue
}
return value.String(), index + 1, true
}
return "", start, false
}
func parseCodexPowerShellSource(source string) (string, string, bool) {
if !strings.HasPrefix(source, "& ") {
return "", "", false
}
runtimePath, next, ok := readCodexPowerShellArgument(source, 2)
if !ok || next >= len(source) || source[next] != ' ' {
return "", "", false
}
scriptPath, end, ok := readCodexPowerShellArgument(source, next+1)
return runtimePath, scriptPath, ok && source[end:] == "; exit $LASTEXITCODE"
}
func windowsPathBase(value string) string {
index := strings.LastIndexAny(value, `\/`)
return value[index+1:]
}
func isAbsoluteWindowsPath(value string) bool {
return len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') ||
(value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' &&
(value[2] == '\\' || value[2] == '/') || strings.HasPrefix(value, `\\`)
}
func decodeCodexPowerShell(encoded string) (string, bool) {
raw, err := base64.StdEncoding.DecodeString(encoded)
if err != nil || base64.StdEncoding.EncodeToString(raw) != encoded || len(raw)%2 != 0 {
return "", false
}
units := make([]uint16, len(raw)/2)
for index := range units {
units[index] = uint16(raw[index*2]) | uint16(raw[index*2+1])<<8
}
return string(utf16.Decode(units)), true
}
func parseCodexWindowsCommand(value any) (string, string, bool) {
command, ok := value.(string)
if !ok || !strings.HasPrefix(command, `"`) {
return "", "", false
}
closing := strings.IndexByte(command[1:], '"')
if closing < 0 {
return "", "", false
}
closing++
powerShell := command[1:closing]
prefix := `" -NoLogo -NoProfile -NonInteractive -EncodedCommand `
if !isAbsoluteWindowsPath(powerShell) ||
!strings.EqualFold(windowsPathBase(powerShell), "powershell.exe") ||
!strings.HasPrefix(command[closing:], prefix) {
return "", "", false
}
encoded := command[closing+len(prefix):]
source, ok := decodeCodexPowerShell(encoded)
if !ok {
return "", "", false
}
return parseCodexPowerShellSource(source)
}
func isCodexNodeExecutable(path string) bool {
name := filepath.Base(path)
return name == "node" || strings.EqualFold(name, "node.exe")
}
func exactCodexHandlerKeys(handler map[string]any) bool { func exactCodexHandlerKeys(handler map[string]any) bool {
if len(handler) != 5 { if len(handler) != 5 {
return false return false
@@ -192,20 +33,21 @@ func codexManagedScriptPath(handler map[string]any, status string) (string, bool
handler["timeout"] != codexHookTimeout || handler["statusMessage"] != status { handler["timeout"] != codexHookTimeout || handler["statusMessage"] != status {
return "", false return "", false
} }
posixRuntime, posixScript, posixOK := parseCodexPOSIXCommand(handler["command"]) command, _ := handler["command"].(string)
windowsRuntime, windowsScript, windowsOK := parseCodexWindowsCommand(handler["commandWindows"]) commandWindows, _ := handler["commandWindows"].(string)
posixRuntime, posixScript, posixOK := parsePOSIXCommand(command)
windowsRuntime, windowsScript, windowsOK := parseEncodedWindowsCommand(commandWindows)
if !windowsOK && posixOK { if !windowsOK && posixOK {
legacy, ok := handler["commandWindows"].(string)
expected := quoteWindowsArgument(posixRuntime) + " " + quoteWindowsArgument(posixScript) expected := quoteWindowsArgument(posixRuntime) + " " + quoteWindowsArgument(posixScript)
if ok && legacy == expected { if commandWindows == expected {
windowsRuntime, windowsScript, windowsOK = posixRuntime, posixScript, true windowsRuntime, windowsScript, windowsOK = posixRuntime, posixScript, true
} }
} }
if !posixOK || !windowsOK || !filepath.IsAbs(posixRuntime) || if !posixOK || !windowsOK || !filepath.IsAbs(posixRuntime) ||
!filepath.IsAbs(posixScript) || !isCodexNodeExecutable(posixRuntime) || !filepath.IsAbs(posixScript) || !isNodeExecutable(posixRuntime) ||
!isCodexNodeExecutable(windowsRuntime) || !isNodeExecutable(windowsRuntime) ||
!sameCodexPath(posixRuntime, windowsRuntime) || !sameManagedPath(posixRuntime, windowsRuntime) ||
!sameCodexPath(posixScript, windowsScript) { !sameManagedPath(posixScript, windowsScript) {
return "", false return "", false
} }
return posixScript, true return posixScript, true
@@ -217,14 +59,12 @@ func codexManagedAgentID(
status string, status string,
) (string, bool) { ) (string, bool) {
scriptPath, managed := codexManagedScriptPath(handler, status) scriptPath, managed := codexManagedScriptPath(handler, status)
if !managed || filepath.Base(scriptPath) != scriptName { if !managed {
return "", false return "", false
} }
agentDirectory := filepath.Dir(scriptPath) reference, err := resolveManagedScript(scriptPath, scriptName)
runtimeRoot, err := AgentRuntimeRoot() if err != nil || reference == nil {
if err != nil || !sameCodexPath(filepath.Dir(agentDirectory), runtimeRoot) {
return "", false return "", false
} }
agentID := filepath.Base(agentDirectory) return reference.agentID, true
return agentID, agentID != "" && agentID != "." && agentID != ".."
} }
+3 -46
View File
@@ -6,9 +6,7 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"sort" "sort"
"strings"
) )
const ( const (
@@ -20,7 +18,6 @@ const (
codexOwnedDescription = "Elydora audit and freeze enforcement" codexOwnedDescription = "Elydora audit and freeze enforcement"
codexGuardStatusMessage = "Checking Elydora agent state" codexGuardStatusMessage = "Checking Elydora agent state"
codexAuditStatusMessage = "Recording Elydora tool use" codexAuditStatusMessage = "Recording Elydora tool use"
codexPOSIXApostrophe = `'"'"'`
) )
type codexHooks map[string][]map[string]any type codexHooks map[string][]map[string]any
@@ -40,11 +37,7 @@ type codexRenderedDocument struct {
remove bool remove bool
} }
type codexRuntimeContract struct { type codexRuntimeContract = managedRuntimeContract
agentID string
guardPath string
auditPath string
}
func cloneCodexObject(value map[string]any) map[string]any { func cloneCodexObject(value map[string]any) map[string]any {
clone := make(map[string]any, len(value)) clone := make(map[string]any, len(value))
@@ -62,28 +55,6 @@ func cloneCodexHooks(source codexHooks) codexHooks {
return clone return clone
} }
func sameCodexPath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if absolute, err := filepath.Abs(left); err == nil {
left = absolute
}
if absolute, err := filepath.Abs(right); err == nil {
right = absolute
}
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameCodexAgentID(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func readCodexHooks(value any, label string) (codexHooks, error) { func readCodexHooks(value any, label string) (codexHooks, error) {
object, ok := value.(map[string]any) object, ok := value.(map[string]any)
if !ok { if !ok {
@@ -126,16 +97,6 @@ func readCodexHooks(value any, label string) (codexHooks, error) {
return hooks, nil return hooks, nil
} }
func decodeStrictJSONObject(source []byte, label string) (map[string]any, error) {
if !json.Valid(source) {
var value any
if err := json.Unmarshal(source, &value); err != nil {
return nil, fmt.Errorf("parse %s: %w", label, err)
}
}
return decodeJSONCObject(source, label, false)
}
func parseCodexDocument(filePath string, raw []byte) (*codexDocument, error) { func parseCodexDocument(filePath string, raw []byte) (*codexDocument, error) {
label := fmt.Sprintf("Codex user hooks at %s", filePath) label := fmt.Sprintf("Codex user hooks at %s", filePath)
root, err := decodeStrictJSONObject(raw, label) root, err := decodeStrictJSONObject(raw, label)
@@ -180,7 +141,7 @@ func removeCodexGroups(
for _, value := range handlers { for _, value := range handlers {
handler := value.(map[string]any) handler := value.(map[string]any)
managedID, managed := codexManagedAgentID(handler, scriptName, status) managedID, managed := codexManagedAgentID(handler, scriptName, status)
if managed && (agentID == "" || sameCodexAgentID(managedID, agentID)) { if managed && (agentID == "" || sameManagedAgentID(managedID, agentID)) {
continue continue
} }
kept = append(kept, handler) kept = append(kept, handler)
@@ -295,11 +256,7 @@ func managedCodexIDs(
if !managed { if !managed {
continue continue
} }
key := agentID result[managedReferenceKey(agentID)] = agentID
if runtime.GOOS == "windows" {
key = strings.ToLower(agentID)
}
result[key] = agentID
} }
} }
return result return result
+18 -129
View File
@@ -1,94 +1,26 @@
package plugins package plugins
import ( import "path/filepath"
"encoding/json"
"fmt"
"os"
"path/filepath"
)
type codexRuntimePaths struct {
runtimeRoot string
agentDirectory string
configPath string
keyPath string
guardPath string
auditPath string
}
func validateCodexInstallConfig(config InstallConfig) error {
for _, field := range []struct{ name, value string }{
{"agent name", config.AgentName},
{"organization ID", config.OrgID},
{"agent ID", config.AgentID},
{"key ID", config.KID},
{"private key", config.PrivateKey},
{"base URL", config.BaseURL},
{"guard script path", config.GuardScriptPath},
} {
if field.value == "" {
return fmt.Errorf("%s is required", field.name)
}
}
if config.AgentName != codexAgentKey {
return fmt.Errorf("codex installation requires agent name %s", codexAgentKey)
}
if err := validateManagedPrivateKey(config.PrivateKey); err != nil {
return err
}
return validateManagedBaseURL(config.BaseURL)
}
func codexAgentPaths(config InstallConfig) (*codexRuntimePaths, error) {
if err := validateCodexInstallConfig(config); err != nil {
return nil, err
}
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return nil, err
}
paths := &codexRuntimePaths{
runtimeRoot: runtimeRoot,
agentDirectory: agentDirectory,
configPath: filepath.Join(agentDirectory, "config.json"),
keyPath: filepath.Join(agentDirectory, "private.key"),
guardPath: filepath.Join(agentDirectory, codexGuardScript),
auditPath: filepath.Join(agentDirectory, codexAuditScript),
}
if !sameCodexPath(config.GuardScriptPath, paths.guardPath) {
return nil, fmt.Errorf(
"guard runtime must use the managed Elydora agent directory: %s",
paths.guardPath,
)
}
if config.HookScript != "" && !sameCodexPath(config.HookScript, paths.auditPath) {
return nil, fmt.Errorf(
"audit runtime must use the managed Elydora agent directory: %s",
paths.auditPath,
)
}
return paths, nil
}
func preflightCodexInstallation( func preflightCodexInstallation(
config InstallConfig, config InstallConfig,
hooksPath string, hooksPath string,
) (*codexRuntimePaths, string, error) { ) (*managedRuntimePaths, string, error) {
paths, err := codexAgentPaths(config) if err := validateManagedInstallConfig(config, codexAgentKey, "Codex"); err != nil {
return nil, "", err
}
paths, err := resolveManagedRuntimePaths(config, codexGuardScript, codexAuditScript)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if _, err := managedPhysicalDirectoryExists( if _, err := managedPhysicalDirectoryExists(
filepath.Dir(hooksPath), filepath.Dir(hooksPath), "Codex hooks directory",
"Codex hooks directory",
); err != nil { ); err != nil {
return nil, "", err return nil, "", err
} }
if err := validateCodexRuntimeIdentity(paths.agentDirectory, config.AgentID); err != nil { if err := validateManagedRuntimeIdentity(
paths.agentDirectory, config.AgentID, codexAgentKey, "Codex",
); err != nil {
return nil, "", err return nil, "", err
} }
nodePath, err := resolveNodeRuntime() nodePath, err := resolveNodeRuntime()
@@ -98,64 +30,21 @@ func preflightCodexInstallation(
return paths, nodePath, nil return paths, nodePath, nil
} }
func buildCodexRuntimeConfig(config InstallConfig) ([]byte, error) {
encoded, err := json.MarshalIndent(agentRuntimeConfig{
OrgID: config.OrgID, AgentID: config.AgentID, KID: config.KID,
BaseURL: config.BaseURL, Token: config.Token, AgentName: codexAgentKey,
}, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
}
encoded = append(encoded, '\n')
if len(encoded) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
return encoded, nil
}
func prepareCodexInstallationChanges( func prepareCodexInstallationChanges(
config InstallConfig, config InstallConfig,
paths *codexRuntimePaths, paths *managedRuntimePaths,
rendered *codexRenderedDocument, rendered *codexRenderedDocument,
) ([]*fileChange, error) { ) ([]*fileChange, error) {
runtimeConfig, err := buildCodexRuntimeConfig(config) changes, err := managedRuntimeFileChanges(
config,
paths,
codexAgentKey,
generateGuardScript(codexAgentKey, config.AgentID, "", false, ""),
buildHookScriptWithOutput(codexAgentKey, config.AgentID, "", false, true),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{
paths.guardPath, "Elydora guard runtime",
[]byte(generateGuardScript(codexAgentKey, config.AgentID, "", false, "")), 0700,
},
{paths.configPath, "Elydora runtime config", runtimeConfig, 0600},
{paths.keyPath, "Elydora private key", []byte(config.PrivateKey), 0600},
{
paths.auditPath, "Elydora audit runtime",
[]byte(buildHookScriptWithOutput(
codexAgentKey, config.AgentID, "", false, true,
)), 0700,
},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
change, err := prepareFileChange(
item.path,
item.label,
item.content,
item.mode,
)
if err != nil {
return nil, err
}
changes = append(changes, change)
}
documentChange, err := prepareRenderedCodexChange(rendered) documentChange, err := prepareRenderedCodexChange(rendered)
if err != nil { if err != nil {
return nil, err return nil, err
+2 -148
View File
@@ -82,152 +82,6 @@ func prepareRenderedCodexChange(rendered *codexRenderedDocument) (*fileChange, e
) )
} }
func writeCodexChanges( func codexRuntimeFilesExist(contracts []managedRuntimeContract) (bool, error) {
changes []*fileChange, return managedRuntimeFilesPresent(contracts, codexAgentKey)
label string,
rename renameFunc,
runtimeRoot string,
agentDirectory string,
hooksDirectory string,
) error {
hasChanges := false
for _, change := range changes {
if change != nil {
hasChanges = true
break
}
}
if !hasChanges {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
if err := ensureManagedDirectory(hooksDirectory, "Codex hooks directory"); err != nil {
return err
}
return writeChanges(changes, label, rename)
}
func readCodexRuntimeConfig(path string) (map[string]any, bool, error) {
snapshot, err := readManagedFile(path, "Elydora runtime config", maxRuntimeConfigBytes)
if err != nil || snapshot == nil {
return nil, snapshot != nil, err
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
config, err := decodeStrictJSONObject(snapshot.contents, label)
return config, true, err
}
func validateCodexRuntimeIdentity(agentDirectory, agentID string) error {
runtimeRoot := filepath.Dir(agentDirectory)
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return err
}
configPath := filepath.Join(agentDirectory, "config.json")
config, configExists, err := readCodexRuntimeConfig(configPath)
if err != nil {
return err
}
artifactExists := false
for _, item := range []struct {
path string
label string
limit int64
}{
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
{filepath.Join(agentDirectory, codexGuardScript), "Elydora guard runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, codexAuditScript), "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "chain-state.json"), "Elydora chain state", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "status-cache.json"), "Elydora status cache", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "error.log"), "Elydora error log", maxManagedSourceBytes},
} {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil {
return inspectErr
}
artifactExists = artifactExists || exists
}
if !configExists {
if artifactExists {
return fmt.Errorf(
"elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != codexAgentKey ||
!sameCodexAgentID(configuredID, agentID) {
return fmt.Errorf(
"elydora runtime config identity does not match Codex agent %s: %s",
agentID,
configPath,
)
}
return nil
}
func codexRuntimeFilesExist(contracts []codexRuntimeContract) (bool, error) {
for _, contract := range contracts {
agentDirectory := filepath.Dir(contract.guardPath)
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil {
return false, err
}
if !directoryExists {
continue
}
config, exists, err := readCodexRuntimeConfig(
filepath.Join(agentDirectory, "config.json"),
)
if err != nil {
return false, err
}
configuredID, idOK := config["agent_id"].(string)
if !exists || !idOK || config["agent_name"] != codexAgentKey ||
!sameCodexAgentID(configuredID, contract.agentID) {
continue
}
complete := true
for _, item := range []struct {
path string
label string
limit int64
}{
{contract.guardPath, "Elydora guard runtime", maxManagedSourceBytes},
{contract.auditPath, "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
} {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil {
return false, inspectErr
}
complete = complete && exists
}
if complete {
return true, nil
}
}
return false, nil
} }
+1 -1
View File
@@ -249,7 +249,7 @@ func TestCodexInstallRollsBackAllFilesOnHooksCommitFailure(t *testing.T) {
source := []byte(`{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"keep"}]}]}}`) source := []byte(`{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"keep"}]}]}}`)
fixture := prepareCodexFixture(t, string(source)) fixture := prepareCodexFixture(t, string(source))
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if sameCodexPath(destination, fixture.configPath) { if sameManagedPath(destination, fixture.configPath) {
return errors.New("injected Codex hooks commit failure") return errors.New("injected Codex hooks commit failure")
} }
return os.Rename(source, destination) return os.Rename(source, destination)
+3 -3
View File
@@ -355,10 +355,10 @@ func TestCodexUninstallPreservesUnrelatedHooksAndExactAgent(t *testing.T) {
if len(requireArray(t, hooks["PreToolUse"])) != 2 || len(requireArray(t, hooks["PostToolUse"])) != 1 { if len(requireArray(t, hooks["PreToolUse"])) != 2 || len(requireArray(t, hooks["PostToolUse"])) != 1 {
t.Fatalf("unexpected remaining hooks: %#v", hooks) t.Fatalf("unexpected remaining hooks: %#v", hooks)
} }
_, remainingScript, parsed := parseCodexPOSIXCommand( _, remainingScript, parsed := parsePOSIXCommand(
requireCodexHandler(t, remaining, "PostToolUse", codexAuditStatus)["command"], requireCodexHandler(t, remaining, "PostToolUse", codexAuditStatus)["command"].(string),
) )
if !parsed || !sameCodexPath(remainingScript, filepath.Join(otherDir, codexAuditScript)) { if !parsed || !sameManagedPath(remainingScript, filepath.Join(otherDir, codexAuditScript)) {
t.Fatalf("other agent handler was removed: %#v", hooks) t.Fatalf("other agent handler was removed: %#v", hooks)
} }
} }
+26 -54
View File
@@ -10,55 +10,27 @@ type CursorPlugin struct {
rename renameFunc rename renameFunc
} }
// ManagesGuardRuntime reports that Cursor commits its provider-specific guard // ManagesGuardRuntime reports that the guard is committed with the audit runtime.
// in the same transaction as the rest of the installation.
func (p *CursorPlugin) ManagesGuardRuntime() bool { func (p *CursorPlugin) ManagesGuardRuntime() bool {
return true return true
} }
func cursorAgentPaths(config InstallConfig) ( func cursorAgentPaths(config InstallConfig) (*managedRuntimePaths, error) {
runtimeRoot, agentDirectory, guardPath, auditPath string, return resolveManagedRuntimePaths(config, cursorGuardScript, cursorAuditScript)
err error,
) {
if config.AgentID == "" {
return "", "", "", "", fmt.Errorf("agent ID is required")
}
runtimeRoot, err = AgentRuntimeRoot()
if err != nil {
return "", "", "", "", err
}
agentDirectory, err = ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return "", "", "", "", err
}
guardPath = filepath.Join(agentDirectory, cursorGuardScript)
if !sameCursorPath(config.GuardScriptPath, guardPath) {
return "", "", "", "", fmt.Errorf(
"Elydora guard runtime must use the managed agent directory: %s",
guardPath,
)
}
auditPath = filepath.Join(agentDirectory, cursorAuditScript)
if config.HookScript != "" && !sameCursorPath(config.HookScript, auditPath) {
return "", "", "", "", fmt.Errorf(
"Elydora audit runtime must use the managed agent directory: %s",
auditPath,
)
}
return runtimeRoot, agentDirectory, guardPath, auditPath, nil
} }
// PreflightInstall validates every existing source before the CLI creates a // PreflightInstall validates every source before the CLI creates runtime state.
// runtime directory or guard file.
func (p *CursorPlugin) PreflightInstall(config InstallConfig) error { func (p *CursorPlugin) PreflightInstall(config InstallConfig) error {
if _, err := readCursorDocument(); err != nil { if _, err := readCursorDocument(); err != nil {
return err return err
} }
_, agentDirectory, _, _, err := cursorAgentPaths(config) paths, err := cursorAgentPaths(config)
if err != nil { if err != nil {
return err return err
} }
if err := preflightCursorRuntime(agentDirectory, config.AgentID); err != nil { if err := validateManagedRuntimeIdentity(
paths.agentDirectory, config.AgentID, cursorAgentKey, "Cursor",
); err != nil {
return err return err
} }
_, err = resolveNodeRuntime() _, err = resolveNodeRuntime()
@@ -70,44 +42,42 @@ func (p *CursorPlugin) Install(config InstallConfig) error {
if err != nil { if err != nil {
return err return err
} }
runtimeRoot, agentDirectory, guardPath, auditPath, err := cursorAgentPaths(config) paths, err := cursorAgentPaths(config)
if err != nil { if err != nil {
return err return err
} }
if err := preflightCursorRuntime(agentDirectory, config.AgentID); err != nil { if err := validateManagedRuntimeIdentity(
paths.agentDirectory, config.AgentID, cursorAgentKey, "Cursor",
); err != nil {
return err return err
} }
nodePath, err := resolveNodeRuntime() nodePath, err := resolveNodeRuntime()
if err != nil { if err != nil {
return err return err
} }
hooks := removeManagedCursorHooks(document.hooks, runtimeRoot, "") hooks := removeManagedCursorHooks(document.hooks, paths.runtimeRoot, "")
hooks["preToolUse"] = append( hooks["preToolUse"] = append(hooks["preToolUse"], buildCursorHandler(nodePath, paths.guardPath))
hooks["preToolUse"], hooks["postToolUse"] = append(hooks["postToolUse"], buildCursorHandler(nodePath, paths.auditPath))
buildCursorHandler(nodePath, guardPath),
)
hooks["postToolUse"] = append(
hooks["postToolUse"],
buildCursorHandler(nodePath, auditPath),
)
hooks["postToolUseFailure"] = append( hooks["postToolUseFailure"] = append(
hooks["postToolUseFailure"], hooks["postToolUseFailure"],
buildCursorHandler(nodePath, auditPath), buildCursorHandler(nodePath, paths.auditPath),
) )
rendered, err := renderCursorDocument(document, hooks, runtimeRoot) rendered, err := renderCursorDocument(document, hooks, paths.runtimeRoot)
if err != nil { if err != nil {
return fmt.Errorf("render Cursor user hooks: %w", err) return fmt.Errorf("render Cursor user hooks: %w", err)
} }
changes, err := prepareCursorInstallationChanges(config, agentDirectory, rendered) changes, err := prepareCursorInstallationChanges(config, paths, rendered)
if err != nil { if err != nil {
return err return err
} }
if err := writeCursorChanges( if err := writeManagedChanges(
changes, changes,
"Install Cursor hooks", "Install Cursor hooks",
p.rename, p.rename,
runtimeRoot, paths.runtimeRoot,
agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath),
"Cursor hooks directory",
); err != nil { ); err != nil {
return err return err
} }
@@ -133,12 +103,14 @@ func (p *CursorPlugin) Uninstall(agentID string) error {
if err != nil { if err != nil {
return err return err
} }
return writeCursorChanges( return writeManagedChanges(
[]*fileChange{change}, []*fileChange{change},
"Uninstall Cursor hooks", "Uninstall Cursor hooks",
p.rename, p.rename,
"", "",
"", "",
filepath.Dir(document.filePath),
"Cursor hooks directory",
) )
} }
+14 -116
View File
@@ -11,12 +11,11 @@ import (
) )
const ( const (
cursorAgentKey = "cursor" cursorAgentKey = "cursor"
cursorConfigFile = "hooks.json" cursorConfigFile = "hooks.json"
cursorGuardScript = "guard.js" cursorGuardScript = "guard.js"
cursorAuditScript = "hook.js" cursorAuditScript = "hook.js"
cursorHookTimeout = float64(10) cursorHookTimeout = float64(10)
cursorPOSIXApostrophe = `'"'"'`
) )
type cursorHooks map[string][]map[string]any type cursorHooks map[string][]map[string]any
@@ -36,11 +35,7 @@ type cursorRenderedDocument struct {
remove bool remove bool
} }
type cursorRuntimeContract struct { type cursorRuntimeContract = managedRuntimeContract
agentID string
guardPath string
auditPath string
}
func cloneCursorHooks(source cursorHooks) cursorHooks { func cloneCursorHooks(source cursorHooks) cursorHooks {
clone := make(cursorHooks, len(source)) clone := make(cursorHooks, len(source))
@@ -50,104 +45,16 @@ func cloneCursorHooks(source cursorHooks) cursorHooks {
return clone return clone
} }
func sameCursorPath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if absolute, err := filepath.Abs(left); err == nil {
left = absolute
}
if absolute, err := filepath.Abs(right); err == nil {
right = absolute
}
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameCursorAgentID(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func quoteCursorPowerShell(value string) string {
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
}
func buildCursorHandler(nodePath, scriptPath string) map[string]any { func buildCursorHandler(nodePath, scriptPath string) map[string]any {
command := quotePOSIXArgument(nodePath) + " " + quotePOSIXArgument(scriptPath) command := posixSource(nodePath, scriptPath)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
command = "& " + quoteCursorPowerShell(nodePath) + " " + command = powerShellSource(nodePath, scriptPath)
quoteCursorPowerShell(scriptPath) + "; exit $LASTEXITCODE"
} }
return map[string]any{ return map[string]any{
"command": command, "timeout": cursorHookTimeout, "failClosed": true, "command": command, "timeout": cursorHookTimeout, "failClosed": true,
} }
} }
func readCursorPOSIXArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); {
if strings.HasPrefix(command[index:], cursorPOSIXApostrophe) {
value.WriteByte('\'')
index += len(cursorPOSIXApostrophe)
continue
}
if command[index] == '\'' {
return value.String(), index + 1, true
}
value.WriteByte(command[index])
index++
}
return "", start, false
}
func readCursorPowerShellArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); index++ {
if command[index] != '\'' {
value.WriteByte(command[index])
continue
}
if index+1 < len(command) && command[index+1] == '\'' {
value.WriteByte('\'')
index++
continue
}
return value.String(), index + 1, true
}
return "", start, false
}
func parseCursorPOSIXCommand(command string) (string, string, bool) {
executable, next, ok := readCursorPOSIXArgument(command, 0)
if !ok || next >= len(command) || command[next] != ' ' {
return "", "", false
}
script, end, ok := readCursorPOSIXArgument(command, next+1)
return executable, script, ok && end == len(command)
}
func parseCursorPowerShellCommand(command string) (string, string, bool) {
if !strings.HasPrefix(command, "& ") {
return "", "", false
}
executable, next, ok := readCursorPowerShellArgument(command, 2)
if !ok || next >= len(command) || command[next] != ' ' {
return "", "", false
}
script, end, ok := readCursorPowerShellArgument(command, next+1)
return executable, script, ok && command[end:] == "; exit $LASTEXITCODE"
}
func parseCursorLegacyCommand(command string) (string, bool) { func parseCursorLegacyCommand(command string) (string, bool) {
separator := strings.IndexByte(command, ' ') separator := strings.IndexByte(command, ' ')
if separator < 0 { if separator < 0 {
@@ -164,11 +71,6 @@ func parseCursorLegacyCommand(command string) (string, bool) {
return script, script != "" && !strings.ContainsAny(script, "\r\n") return script, script != "" && !strings.ContainsAny(script, "\r\n")
} }
func isCursorNodeExecutable(path string) bool {
name := filepath.Base(path)
return name == "node" || strings.EqualFold(name, "node.exe")
}
func cursorManagedScriptPath(handler map[string]any) (string, bool) { func cursorManagedScriptPath(handler map[string]any) (string, bool) {
if len(handler) == 3 && handler["timeout"] == cursorHookTimeout && handler["failClosed"] == true { if len(handler) == 3 && handler["timeout"] == cursorHookTimeout && handler["failClosed"] == true {
command, ok := handler["command"].(string) command, ok := handler["command"].(string)
@@ -178,12 +80,12 @@ func cursorManagedScriptPath(handler map[string]any) (string, bool) {
var nodePath, scriptPath string var nodePath, scriptPath string
var parsed bool var parsed bool
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
nodePath, scriptPath, parsed = parseCursorPowerShellCommand(command) nodePath, scriptPath, parsed = parsePowerShellSource(command)
} else { } else {
nodePath, scriptPath, parsed = parseCursorPOSIXCommand(command) nodePath, scriptPath, parsed = parsePOSIXCommand(command)
} }
if parsed && filepath.IsAbs(nodePath) && filepath.IsAbs(scriptPath) && if parsed && filepath.IsAbs(nodePath) && filepath.IsAbs(scriptPath) &&
isCursorNodeExecutable(nodePath) { isNodeExecutable(nodePath) {
return scriptPath, true return scriptPath, true
} }
} }
@@ -207,7 +109,7 @@ func cursorManagedAgentID(
return "", false return "", false
} }
agentDirectory := filepath.Dir(scriptPath) agentDirectory := filepath.Dir(scriptPath)
if !sameCursorPath(filepath.Dir(agentDirectory), runtimeRoot) { if !sameManagedPath(filepath.Dir(agentDirectory), runtimeRoot) {
return "", false return "", false
} }
agentID := filepath.Base(agentDirectory) agentID := filepath.Base(agentDirectory)
@@ -297,7 +199,7 @@ func removeManagedCursorHooks(hooks cursorHooks, runtimeRoot, agentID string) cu
kept := make([]map[string]any, 0, len(result[contract.event])) kept := make([]map[string]any, 0, len(result[contract.event]))
for _, handler := range result[contract.event] { for _, handler := range result[contract.event] {
managedID, managed := cursorManagedAgentID(handler, contract.script, runtimeRoot) managedID, managed := cursorManagedAgentID(handler, contract.script, runtimeRoot)
if managed && (agentID == "" || sameCursorAgentID(managedID, agentID)) { if managed && (agentID == "" || sameManagedAgentID(managedID, agentID)) {
continue continue
} }
kept = append(kept, handler) kept = append(kept, handler)
@@ -384,11 +286,7 @@ func managedCursorIDs(
if !managed { if !managed {
continue continue
} }
key := agentID result[managedReferenceKey(agentID)] = agentID
if runtime.GOOS == "windows" {
key = strings.ToLower(agentID)
}
result[key] = agentID
} }
return result return result
} }
+21 -266
View File
@@ -1,13 +1,9 @@
package plugins package plugins
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings"
) )
func cursorConfigPath() (string, error) { func cursorConfigPath() (string, error) {
@@ -27,21 +23,14 @@ func readCursorDocument() (*cursorDocument, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
info, err := os.Lstat(filePath) snapshot, err := readManagedFile(filePath, "Cursor user hooks", maxManagedSourceBytes)
if errors.Is(err, os.ErrNotExist) { if err != nil {
return nil, err
}
if snapshot == nil {
return createCursorDocument(filePath), nil return createCursorDocument(filePath), nil
} }
if err != nil { return parseCursorDocument(filePath, snapshot.contents, runtimeRoot)
return nil, fmt.Errorf("inspect Cursor user hooks at %s: %w", filePath, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return nil, fmt.Errorf("Cursor user hooks path is not a physical file: %s", filePath)
}
raw, err := os.ReadFile(filePath) // #nosec G304 -- filePath is the fixed Cursor user configuration path.
if err != nil {
return nil, fmt.Errorf("read Cursor user hooks at %s: %w", filePath, err)
}
return parseCursorDocument(filePath, raw, runtimeRoot)
} }
func prepareRenderedCursorChange(rendered *cursorRenderedDocument) (*fileChange, error) { func prepareRenderedCursorChange(rendered *cursorRenderedDocument) (*fileChange, error) {
@@ -59,167 +48,25 @@ func prepareRenderedCursorChange(rendered *cursorRenderedDocument) (*fileChange,
) )
} }
func cursorPhysicalFileExists(path, label string) (bool, error) {
info, err := os.Lstat(path)
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect %s at %s: %w", label, path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return false, fmt.Errorf("%s path is not a physical file: %s", label, path)
}
return true, nil
}
func cursorPhysicalDirectoryExists(path string) (bool, error) {
info, err := os.Lstat(path)
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect Elydora runtime directory at %s: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return false, fmt.Errorf("Elydora runtime path is not a physical directory: %s", path)
}
return true, nil
}
func readCursorRuntimeConfig(path string) (map[string]any, bool, error) {
exists, err := cursorPhysicalFileExists(path, "Elydora runtime config")
if err != nil || !exists {
return nil, exists, err
}
raw, err := os.ReadFile(path) // #nosec G304 -- path is confined to one validated Elydora agent directory.
if err != nil {
return nil, true, fmt.Errorf("read Elydora runtime config at %s: %w", path, err)
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
if !json.Valid(raw) {
var value any
if err := json.Unmarshal(raw, &value); err != nil {
return nil, true, fmt.Errorf("parse %s: %w", label, err)
}
}
config, err := decodeJSONCObject(raw, label, false)
if err != nil {
return nil, true, err
}
return config, true, nil
}
func validateCursorRuntimeIdentity(path, agentID string) (bool, error) {
config, exists, err := readCursorRuntimeConfig(path)
if err != nil || !exists {
return exists, err
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != cursorAgentKey ||
!sameCursorAgentID(configuredID, agentID) {
return true, fmt.Errorf(
"Elydora runtime config identity does not match Cursor agent %s: %s",
agentID,
path,
)
}
return true, nil
}
func preflightCursorRuntime(agentDirectory, agentID string) error {
exists, err := cursorPhysicalDirectoryExists(agentDirectory)
if err != nil || !exists {
return err
}
identityExists, err := validateCursorRuntimeIdentity(
filepath.Join(agentDirectory, "config.json"), agentID,
)
if err != nil {
return err
}
runtimeExists := false
for _, item := range []struct{ path, label string }{
{filepath.Join(agentDirectory, cursorGuardScript), "Elydora guard runtime"},
{filepath.Join(agentDirectory, cursorAuditScript), "Elydora audit runtime"},
{filepath.Join(agentDirectory, "private.key"), "Elydora private key"},
} {
exists, err := cursorPhysicalFileExists(item.path, item.label)
if err != nil {
return err
}
runtimeExists = runtimeExists || exists
}
if runtimeExists && !identityExists {
return fmt.Errorf(
"Elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
func prepareCursorInstallationChanges( func prepareCursorInstallationChanges(
config InstallConfig, config InstallConfig,
agentDirectory string, paths *managedRuntimePaths,
rendered *cursorRenderedDocument, rendered *cursorRenderedDocument,
) ([]*fileChange, error) { ) ([]*fileChange, error) {
baseURL := config.BaseURL if config.BaseURL == "" {
if baseURL == "" { config.BaseURL = "https://api.elydora.com"
baseURL = "https://api.elydora.com"
} }
runtimeConfig, err := json.MarshalIndent(agentRuntimeConfig{ changes, err := managedRuntimeFileChanges(
OrgID: config.OrgID, AgentID: config.AgentID, KID: config.KID, config,
BaseURL: baseURL, Token: config.Token, AgentName: cursorAgentKey, paths,
}, "", " ") cursorAgentKey,
generateGuardScript(
cursorAgentKey, config.AgentID, `{"permission":"allow"}`+"\n", true, "cursor",
),
buildHookScriptWithOutput(cursorAgentKey, config.AgentID, "{}\n", true, true),
)
if err != nil { if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err) return nil, err
}
runtimeConfig = append(runtimeConfig, '\n')
if len(runtimeConfig) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"Elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{
filepath.Join(agentDirectory, cursorGuardScript), "Elydora guard runtime",
[]byte(generateGuardScript(
cursorAgentKey,
config.AgentID,
`{"permission":"allow"}`+"\n",
true,
"cursor",
)), 0700,
},
{filepath.Join(agentDirectory, "config.json"), "Elydora runtime config", runtimeConfig, 0600},
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", []byte(config.PrivateKey), 0600},
{
filepath.Join(agentDirectory, cursorAuditScript), "Elydora audit runtime",
[]byte(buildHookScriptWithOutput(
cursorAgentKey,
config.AgentID,
"{}\n",
true,
true,
)), 0700,
},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
if err := validateRuntimeFileTarget(item.path, item.label); err != nil {
return nil, err
}
change, err := prepareFileChange(item.path, item.label, item.content, item.mode)
if err != nil {
return nil, err
}
changes = append(changes, change)
} }
documentChange, err := prepareRenderedCursorChange(rendered) documentChange, err := prepareRenderedCursorChange(rendered)
if err != nil { if err != nil {
@@ -228,98 +75,6 @@ func prepareCursorInstallationChanges(
return append(changes, documentChange), nil return append(changes, documentChange), nil
} }
func ensureCursorDirectory(path, label string) error { func cursorRuntimeFilesExist(contracts []managedRuntimeContract) (bool, error) {
if err := os.MkdirAll(path, 0700); err != nil { return managedRuntimeFilesPresent(contracts, cursorAgentKey)
return fmt.Errorf("create %s directory at %s: %w", label, path, err)
}
info, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect %s directory at %s: %w", label, path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return fmt.Errorf("%s directory is not a physical directory: %s", label, path)
}
return nil
}
func writeCursorChanges(
changes []*fileChange,
label string,
rename renameFunc,
runtimeRoot, agentDirectory string,
) error {
hasChanges := false
for _, change := range changes {
if change != nil {
hasChanges = true
break
}
}
if !hasChanges {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
directories := map[string]string{}
for _, change := range changes {
if change == nil {
continue
}
directory := filepath.Dir(change.filePath)
key := filepath.Clean(directory)
if runtime.GOOS == "windows" {
key = strings.ToLower(key)
}
directories[key] = directory
}
for _, directory := range directories {
if err := ensureCursorDirectory(directory, label); err != nil {
return err
}
}
return writeChanges(changes, label, rename)
}
func cursorRuntimeFilesExist(contracts []cursorRuntimeContract) (bool, error) {
for _, contract := range contracts {
agentDirectory := filepath.Dir(contract.guardPath)
directoryExists, err := cursorPhysicalDirectoryExists(agentDirectory)
if err != nil {
return false, err
}
if !directoryExists {
continue
}
config, exists, err := readCursorRuntimeConfig(filepath.Join(agentDirectory, "config.json"))
if err != nil {
return false, err
}
configuredID, idOK := config["agent_id"].(string)
if !exists || !idOK || config["agent_name"] != cursorAgentKey ||
!sameCursorAgentID(configuredID, contract.agentID) {
continue
}
complete := true
for _, item := range []struct{ path, label string }{
{contract.guardPath, "Elydora guard runtime"},
{contract.auditPath, "Elydora audit runtime"},
{filepath.Join(agentDirectory, "private.key"), "Elydora private key"},
} {
exists, err := cursorPhysicalFileExists(item.path, item.label)
if err != nil {
return false, err
}
complete = complete && exists
}
if complete {
return true, nil
}
}
return false, nil
} }
+2 -2
View File
@@ -183,7 +183,7 @@ func TestCursorInstallCreatesRuntimeDirectoryTransactionally(t *testing.T) {
fixture.guardPath, fixture.guardPath,
fixture.hookPath, fixture.hookPath,
} { } {
if exists, err := cursorPhysicalFileExists(path, "Cursor runtime"); err != nil || !exists { if exists, err := managedPhysicalFileExists(path, "Cursor runtime", maxManagedSourceBytes); err != nil || !exists {
t.Fatalf("runtime file %s exists = %v, %v", path, exists, err) t.Fatalf("runtime file %s exists = %v, %v", path, exists, err)
} }
} }
@@ -200,7 +200,7 @@ func TestCursorInstallRollsBackEveryFileOnConfigCommitFailure(t *testing.T) {
} }
failed := false failed := false
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if !failed && sameCursorPath(destination, fixture.configPath) { if !failed && sameManagedPath(destination, fixture.configPath) {
failed = true failed = true
return errors.New("injected Cursor config commit failure") return errors.New("injected Cursor config commit failure")
} }
+1 -1
View File
@@ -342,7 +342,7 @@ func TestCursorCreatesMissingGuardAndRejectsUnmanagedRuntimePaths(t *testing.T)
fixture.config.HookScript = unmanaged fixture.config.HookScript = unmanaged
} }
if err := fixture.plugin.Install(fixture.config); err == nil || if err := fixture.plugin.Install(fixture.config); err == nil ||
!strings.Contains(err.Error(), "managed agent directory") { !strings.Contains(err.Error(), "managed Elydora agent directory") {
t.Fatalf("unmanaged %s error = %v", field, err) t.Fatalf("unmanaged %s error = %v", field, err)
} }
if _, err := os.Stat(fixture.configPath); !os.IsNotExist(err) { if _, err := os.Stat(fixture.configPath); !os.IsNotExist(err) {
+5 -4
View File
@@ -10,8 +10,7 @@ type GeminiPlugin struct {
rename renameFunc rename renameFunc
} }
// ManagesGuardRuntime reports that Gemini commits both generated runtimes and // ManagesGuardRuntime reports that the guard is committed with the audit runtime.
// the user settings document through one transaction.
func (p *GeminiPlugin) ManagesGuardRuntime() bool { func (p *GeminiPlugin) ManagesGuardRuntime() bool {
return true return true
} }
@@ -92,13 +91,14 @@ func (p *GeminiPlugin) Install(config InstallConfig) error {
if err != nil { if err != nil {
return err return err
} }
if err := writeGeminiChanges( if err := writeManagedChanges(
changes, changes,
"Install Gemini CLI hooks", "Install Gemini CLI hooks",
p.rename, p.rename,
paths.runtimeRoot, paths.runtimeRoot,
paths.agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Gemini CLI configuration directory",
); err != nil { ); err != nil {
return err return err
} }
@@ -120,13 +120,14 @@ func (p *GeminiPlugin) Uninstall(agentID string) error {
if err != nil { if err != nil {
return err return err
} }
return writeGeminiChanges( return writeManagedChanges(
[]*fileChange{change}, []*fileChange{change},
"Uninstall Gemini CLI hooks", "Uninstall Gemini CLI hooks",
p.rename, p.rename,
"", "",
"", "",
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Gemini CLI configuration directory",
) )
} }
+15 -74
View File
@@ -7,83 +7,37 @@ import (
"strings" "strings"
) )
type geminiRuntimeReference struct { type geminiRuntimeReference = managedScriptReference
agentID string
scriptPath string
}
func sameGeminiPath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if absolute, err := filepath.Abs(left); err == nil {
left = absolute
}
if absolute, err := filepath.Abs(right); err == nil {
right = absolute
}
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameGeminiAgentID(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameGeminiFileName(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func isGeminiNodeExecutable(path string) bool {
name := filepath.Base(path)
return name == "node" || strings.EqualFold(name, "node.exe")
}
func buildGeminiCommand(runtimePath, scriptPath string) (string, error) { func buildGeminiCommand(runtimePath, scriptPath string) (string, error) {
if !filepath.IsAbs(runtimePath) || !filepath.IsAbs(scriptPath) { if !filepath.IsAbs(runtimePath) || !filepath.IsAbs(scriptPath) {
return "", fmt.Errorf( return "", fmt.Errorf("Gemini CLI hook commands require absolute runtime and script paths")
"Gemini CLI hook commands require absolute runtime and script paths",
)
} }
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil return posixSource(runtimePath, scriptPath), nil
} }
source := "& " + quoteCodexPowerShell(runtimePath) + " " + return "& " + quotePowerShellArgument(windowsPowerShellPath()) + powerShellEncodedFlags +
quoteCodexPowerShell(scriptPath) + "; exit $LASTEXITCODE" encodePowerShellSource(powerShellSource(runtimePath, scriptPath)), nil
return "& " + quoteCodexPowerShell(codexPowerShellPath()) +
" -NoLogo -NoProfile -NonInteractive -EncodedCommand " +
encodeCodexPowerShell(source), nil
} }
func parseGeminiWindowsCommand(command string) (string, string, bool) { func parseGeminiWindowsCommand(command string) (string, string, bool) {
if !strings.HasPrefix(command, "& ") { if !strings.HasPrefix(command, "& ") {
return "", "", false return "", "", false
} }
powerShell, next, ok := readCodexPowerShellArgument(command, 2) powerShell, next, ok := readPowerShellArgument(command, 2)
if !ok || !isAbsoluteWindowsPath(powerShell) || if !ok || !isPowerShellExecutable(powerShell) ||
!strings.EqualFold(windowsPathBase(powerShell), "powershell.exe") { !strings.HasPrefix(command[next:], powerShellEncodedFlags) {
return "", "", false return "", "", false
} }
prefix := " -NoLogo -NoProfile -NonInteractive -EncodedCommand " source, ok := decodePowerShellSource(command[next+len(powerShellEncodedFlags):])
if !strings.HasPrefix(command[next:], prefix) {
return "", "", false
}
source, ok := decodeCodexPowerShell(command[next+len(prefix):])
if !ok { if !ok {
return "", "", false return "", "", false
} }
return parseCodexPowerShellSource(source) return parsePowerShellSource(source)
} }
func parseGeminiCommand(command string) (string, string, bool) { func parseGeminiCommand(command string) (string, string, bool) {
if runtimePath, scriptPath, ok := parseCodexPOSIXCommand(command); ok { if runtimePath, scriptPath, ok := parsePOSIXCommand(command); ok {
return runtimePath, scriptPath, true return runtimePath, scriptPath, true
} }
return parseGeminiWindowsCommand(command) return parseGeminiWindowsCommand(command)
@@ -110,23 +64,10 @@ func geminiRuntimeReferenceForCommand(
if !current && includeLegacy { if !current && includeLegacy {
runtimePath, scriptPath, legacy = parseLegacyGeminiCommand(command) runtimePath, scriptPath, legacy = parseLegacyGeminiCommand(command)
} }
validRuntime := (current && filepath.IsAbs(runtimePath) && validRuntime := (current && filepath.IsAbs(runtimePath) && isNodeExecutable(runtimePath)) ||
isGeminiNodeExecutable(runtimePath)) || (legacy && runtimePath == "node") (legacy && runtimePath == "node")
if !validRuntime || !filepath.IsAbs(scriptPath) || if !validRuntime {
!sameGeminiFileName(filepath.Base(scriptPath), scriptName) {
return nil, nil return nil, nil
} }
agentDirectory := filepath.Dir(scriptPath) return resolveManagedScript(scriptPath, scriptName)
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
if !sameGeminiPath(filepath.Dir(agentDirectory), runtimeRoot) {
return nil, nil
}
agentID := filepath.Base(agentDirectory)
if agentID == "" || agentID == "." || agentID == ".." {
return nil, nil
}
return &geminiRuntimeReference{agentID: agentID, scriptPath: scriptPath}, nil
} }
+3 -25
View File
@@ -1,10 +1,7 @@
package plugins package plugins
import ( import (
"path/filepath"
"runtime"
"sort" "sort"
"strings"
) )
const ( const (
@@ -27,11 +24,7 @@ type geminiManagedRemoval struct {
removeGroup bool removeGroup bool
} }
type geminiRuntimeContract struct { type geminiRuntimeContract = managedRuntimeContract
agentID string
guardPath string
auditPath string
}
func buildGeminiGroup(runtimePath, scriptPath, name string) (map[string]any, error) { func buildGeminiGroup(runtimePath, scriptPath, name string) (map[string]any, error) {
command, err := buildGeminiCommand(runtimePath, scriptPath) command, err := buildGeminiCommand(runtimePath, scriptPath)
@@ -151,7 +144,7 @@ func managedGeminiRemovals(
return nil, err return nil, err
} }
if reference != nil && if reference != nil &&
(agentID == "" || sameGeminiAgentID(reference.agentID, agentID)) { (agentID == "" || sameManagedAgentID(reference.agentID, agentID)) {
indexes = append(indexes, handlerIndex) indexes = append(indexes, handlerIndex)
} }
} }
@@ -168,13 +161,6 @@ func managedGeminiRemovals(
return removals, nil return removals, nil
} }
func geminiReferenceKey(agentID string) string {
if runtime.GOOS == "windows" {
return strings.ToLower(agentID)
}
return agentID
}
func geminiReferencesForEvent( func geminiReferencesForEvent(
groups []any, groups []any,
scriptName string, scriptName string,
@@ -196,7 +182,7 @@ func geminiReferencesForEvent(
return nil, err return nil, err
} }
if reference != nil { if reference != nil {
key := geminiReferenceKey(reference.agentID) key := managedReferenceKey(reference.agentID)
result[key] = append(result[key], *reference) result[key] = append(result[key], *reference)
} }
} }
@@ -243,11 +229,3 @@ func geminiRuntimeContracts(
} }
return contracts, nil return contracts, nil
} }
func geminiRuntimeRoot() (string, error) {
root, err := AgentRuntimeRoot()
if err != nil {
return "", err
}
return filepath.Clean(root), nil
}
+19 -161
View File
@@ -1,190 +1,48 @@
package plugins package plugins
import ( import "fmt"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type geminiRuntimePaths struct {
runtimeRoot string
agentDirectory string
configPath string
keyPath string
guardPath string
auditPath string
}
func validateGeminiInstallConfig(config InstallConfig) error {
for _, field := range []struct{ name, value string }{
{"agent name", config.AgentName},
{"organization ID", config.OrgID},
{"agent ID", config.AgentID},
{"key ID", config.KID},
{"private key", config.PrivateKey},
{"base URL", config.BaseURL},
{"guard script path", config.GuardScriptPath},
} {
if field.value == "" {
return fmt.Errorf("%s is required", field.name)
}
}
if config.AgentName != geminiAgentKey {
return fmt.Errorf(
"Gemini CLI installation requires agent name %s",
geminiAgentKey,
)
}
if strings.TrimSpace(config.OrgID) == "" {
return fmt.Errorf("organization ID is required")
}
if strings.TrimSpace(config.KID) == "" {
return fmt.Errorf("key ID is required")
}
if config.Token != "" && strings.TrimSpace(config.Token) == "" {
return fmt.Errorf("token must contain a non-whitespace value when provided")
}
if err := validateManagedPrivateKey(config.PrivateKey); err != nil {
return err
}
return validateManagedBaseURL(config.BaseURL)
}
func geminiAgentPaths(config InstallConfig) (*geminiRuntimePaths, error) {
if err := validateGeminiInstallConfig(config); err != nil {
return nil, err
}
runtimeRoot, err := geminiRuntimeRoot()
if err != nil {
return nil, err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return nil, err
}
paths := &geminiRuntimePaths{
runtimeRoot: runtimeRoot, agentDirectory: agentDirectory,
configPath: filepath.Join(agentDirectory, "config.json"),
keyPath: filepath.Join(agentDirectory, "private.key"),
guardPath: filepath.Join(agentDirectory, geminiGuardScript),
auditPath: filepath.Join(agentDirectory, geminiAuditScript),
}
if !filepath.IsAbs(config.GuardScriptPath) ||
!sameGeminiPath(config.GuardScriptPath, paths.guardPath) {
return nil, fmt.Errorf(
"guard runtime must use the managed Elydora agent directory: %s",
paths.guardPath,
)
}
if config.HookScript != "" && (!filepath.IsAbs(config.HookScript) ||
!sameGeminiPath(config.HookScript, paths.auditPath)) {
return nil, fmt.Errorf(
"audit runtime must use the managed Elydora agent directory: %s",
paths.auditPath,
)
}
return paths, nil
}
func preflightGeminiInstallation( func preflightGeminiInstallation(
config InstallConfig, config InstallConfig,
document *geminiDocument, document *geminiDocument,
) (*geminiRuntimePaths, string, error) { ) (*managedRuntimePaths, string, error) {
if document == nil || document.filePath == "" { if document == nil || document.filePath == "" {
return nil, "", fmt.Errorf( return nil, "", fmt.Errorf("Gemini CLI installation requires a user settings document")
"Gemini CLI installation requires a user settings document",
)
} }
paths, err := geminiAgentPaths(config) if err := validateManagedInstallConfig(config, geminiAgentKey, "Gemini CLI"); err != nil {
return nil, "", err
}
paths, err := resolveManagedRuntimePaths(config, geminiGuardScript, geminiAuditScript)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if err := validateGeminiRuntimeIdentity(paths.agentDirectory, config.AgentID); err != nil { if err := validateManagedRuntimeIdentity(
paths.agentDirectory, config.AgentID, geminiAgentKey, "Gemini CLI",
); err != nil {
return nil, "", err return nil, "", err
} }
nodePath, err := resolveNodeRuntime() nodePath, err := resolveAbsoluteNodeRuntime("Gemini CLI")
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if !filepath.IsAbs(nodePath) || !isGeminiNodeExecutable(nodePath) {
return nil, "", fmt.Errorf(
"Gemini CLI hooks require an absolute Node.js executable path",
)
}
return paths, nodePath, nil return paths, nodePath, nil
} }
func buildGeminiRuntimeConfig(config InstallConfig) ([]byte, error) {
value := map[string]any{
"org_id": config.OrgID, "agent_id": config.AgentID, "kid": config.KID,
"base_url": config.BaseURL, "agent_name": geminiAgentKey,
}
if config.Token != "" {
value["token"] = config.Token
}
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
}
encoded = append(encoded, '\n')
if len(encoded) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
return encoded, nil
}
func prepareGeminiInstallationChanges( func prepareGeminiInstallationChanges(
config InstallConfig, config InstallConfig,
paths *geminiRuntimePaths, paths *managedRuntimePaths,
rendered *geminiRenderedDocument, rendered *geminiRenderedDocument,
) ([]*fileChange, error) { ) ([]*fileChange, error) {
runtimeConfig, err := buildGeminiRuntimeConfig(config) changes, err := managedRuntimeFileChanges(
config,
paths,
geminiAgentKey,
generateGuardScript(geminiAgentKey, config.AgentID, "{}\n", false, ""),
buildHookScriptWithOutput(geminiAgentKey, config.AgentID, "{}\n", false, true),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{
paths.guardPath, "Elydora guard runtime",
[]byte(generateGuardScript(
geminiAgentKey,
config.AgentID,
"{}\n",
false,
"",
)),
0700,
},
{paths.configPath, "Elydora runtime config", runtimeConfig, 0600},
{paths.keyPath, "Elydora private key", []byte(config.PrivateKey), 0600},
{
paths.auditPath, "Elydora audit runtime",
[]byte(buildHookScriptWithOutput(
geminiAgentKey,
config.AgentID,
"{}\n",
false,
true,
)),
0700,
},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
change, err := prepareFileChange(item.path, item.label, item.content, item.mode)
if err != nil {
return nil, err
}
changes = append(changes, change)
}
documentChange, err := prepareRenderedGeminiChange(rendered) documentChange, err := prepareRenderedGeminiChange(rendered)
if err != nil { if err != nil {
return nil, err return nil, err
-35
View File
@@ -67,38 +67,3 @@ func prepareRenderedGeminiChange(
rendered.remove, rendered.remove,
) )
} }
func writeGeminiChanges(
changes []*fileChange,
label string,
rename renameFunc,
runtimeRoot string,
agentDirectory string,
settingsDirectory string,
) error {
hasChanges := false
for _, change := range changes {
if change != nil {
hasChanges = true
break
}
}
if !hasChanges {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
if err := ensureManagedDirectory(
settingsDirectory,
"Gemini CLI configuration directory",
); err != nil {
return err
}
return writeChanges(changes, label, rename)
}
+2 -228
View File
@@ -1,231 +1,5 @@
package plugins package plugins
import ( func geminiRuntimeFilesExist(contracts []managedRuntimeContract) (bool, error) {
"fmt" return managedRuntimeFilesExist(contracts, geminiAgentKey, "Gemini CLI", nil)
"path/filepath"
"sort"
"strings"
)
func readGeminiRuntimeConfig(path string) (map[string]any, bool, error) {
snapshot, err := readManagedFile(path, "Elydora runtime config", maxRuntimeConfigBytes)
if err != nil || snapshot == nil {
return nil, snapshot != nil, err
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
config, err := decodeStrictJSONObject(snapshot.contents, label)
return config, true, err
}
func requireGeminiRuntimeString(
config map[string]any,
field string,
configPath string,
) (string, error) {
value, ok := config[field].(string)
if !ok || strings.TrimSpace(value) == "" {
return "", fmt.Errorf(
"elydora runtime config %s is invalid: %s",
field,
configPath,
)
}
return value, nil
}
func validateGeminiRuntimeConfig(
config map[string]any,
expectedAgentID string,
configPath string,
) error {
supported := stringSet(
"org_id", "agent_id", "kid", "base_url", "token", "agent_name",
)
fields := make([]string, 0, len(config))
for field := range config {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
if _, ok := supported[field]; !ok {
return fmt.Errorf(
`elydora runtime config has unsupported field %q: %s`,
field,
configPath,
)
}
}
if _, err := requireGeminiRuntimeString(config, "org_id", configPath); err != nil {
return err
}
if _, err := requireGeminiRuntimeString(config, "kid", configPath); err != nil {
return err
}
agentID, err := requireGeminiRuntimeString(config, "agent_id", configPath)
if err != nil {
return err
}
if !sameGeminiAgentID(agentID, expectedAgentID) ||
config["agent_name"] != geminiAgentKey {
return fmt.Errorf(
"elydora runtime identity does not match Gemini CLI hooks: %s",
configPath,
)
}
if _, exists := config["token"]; exists {
if _, err := requireGeminiRuntimeString(config, "token", configPath); err != nil {
return err
}
}
baseURL, err := requireGeminiRuntimeString(config, "base_url", configPath)
if err != nil {
return err
}
if err := validateManagedBaseURL(baseURL); err != nil {
return fmt.Errorf(
"elydora runtime config base URL is invalid at %s: %w",
configPath,
err,
)
}
return nil
}
func validateGeminiRuntimeIdentity(agentDirectory, agentID string) error {
runtimeRoot := filepath.Dir(agentDirectory)
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return err
}
configPath := filepath.Join(agentDirectory, "config.json")
config, configExists, err := readGeminiRuntimeConfig(configPath)
if err != nil {
return err
}
artifactExists := false
for _, item := range []struct {
path string
label string
limit int64
}{
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
{filepath.Join(agentDirectory, geminiGuardScript), "Elydora guard runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, geminiAuditScript), "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "chain-state.json"), "Elydora chain state", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "status-cache.json"), "Elydora status cache", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "error.log"), "Elydora error log", maxManagedSourceBytes},
} {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil {
return inspectErr
}
artifactExists = artifactExists || exists
}
if !configExists {
if artifactExists {
return fmt.Errorf(
"elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != geminiAgentKey ||
!sameGeminiAgentID(configuredID, agentID) {
return fmt.Errorf(
"elydora runtime config identity does not match Gemini CLI agent %s: %s",
agentID,
configPath,
)
}
return nil
}
func geminiRuntimeContractExists(contract geminiRuntimeContract) (bool, error) {
runtimeRoot, err := geminiRuntimeRoot()
if err != nil {
return false, err
}
agentDirectory := filepath.Dir(contract.guardPath)
if !sameGeminiPath(filepath.Dir(agentDirectory), runtimeRoot) ||
!sameGeminiPath(
contract.auditPath,
filepath.Join(agentDirectory, geminiAuditScript),
) {
return false, nil
}
rootExists, err := managedPhysicalDirectoryExists(
runtimeRoot,
"Elydora runtime directory",
)
if err != nil || !rootExists {
return false, err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory,
"Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return false, err
}
configPath := filepath.Join(agentDirectory, "config.json")
keyPath := filepath.Join(agentDirectory, "private.key")
config, configExists, err := readGeminiRuntimeConfig(configPath)
if err != nil {
return false, err
}
key, err := readManagedFile(keyPath, "Elydora private key", maxProtectedSecretBytes)
if err != nil {
return false, err
}
guard, err := readManagedFile(
contract.guardPath,
"Elydora guard runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
audit, err := readManagedFile(
contract.auditPath,
"Elydora audit runtime",
maxManagedSourceBytes,
)
if err != nil {
return false, err
}
if !configExists || key == nil || guard == nil || audit == nil {
return false, nil
}
if err := validateGeminiRuntimeConfig(config, contract.agentID, configPath); err != nil {
return false, err
}
if err := validateManagedPrivateKey(string(key.contents)); err != nil {
return false, fmt.Errorf("elydora private key at %s: %w", keyPath, err)
}
return len(guard.contents) > 0 && len(audit.contents) > 0, nil
}
func geminiRuntimeFilesExist(contracts []geminiRuntimeContract) (bool, error) {
for _, contract := range contracts {
exists, err := geminiRuntimeContractExists(contract)
if err != nil {
return false, err
}
if exists {
return true, nil
}
}
return false, nil
} }
+4 -4
View File
@@ -33,8 +33,8 @@ func TestGeminiCommandRoundTripAndRejectsInjection(t *testing.T) {
t.Fatalf("build Gemini command: %v", err) t.Fatalf("build Gemini command: %v", err)
} }
executable, script, ok := parseGeminiCommand(command) executable, script, ok := parseGeminiCommand(command)
if !ok || !sameGeminiPath(executable, nodePath) || if !ok || !sameManagedPath(executable, nodePath) ||
!sameGeminiPath(script, fixture.guardPath) { !sameManagedPath(script, fixture.guardPath) {
t.Fatalf("parsed Gemini command = %q, %q, %v", executable, script, ok) t.Fatalf("parsed Gemini command = %q, %q, %v", executable, script, ok)
} }
if runtime.GOOS == "windows" && (strings.Contains(command, "%GEMINI_CWD%") || if runtime.GOOS == "windows" && (strings.Contains(command, "%GEMINI_CWD%") ||
@@ -425,12 +425,12 @@ func TestGeminiRuntimeConfigOmitsEmptyOptionalToken(t *testing.T) {
func TestGeminiAgentIDMatchingIsPlatformCorrect(t *testing.T) { func TestGeminiAgentIDMatchingIsPlatformCorrect(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if !sameGeminiAgentID("AGENT-1", "agent-1") { if !sameManagedAgentID("AGENT-1", "agent-1") {
t.Fatal("Windows Gemini agent IDs should compare case-insensitively") t.Fatal("Windows Gemini agent IDs should compare case-insensitively")
} }
return return
} }
if sameGeminiAgentID("AGENT-1", "agent-1") { if sameManagedAgentID("AGENT-1", "agent-1") {
t.Fatal("POSIX Gemini agent IDs should preserve case") t.Fatal("POSIX Gemini agent IDs should preserve case")
} }
} }
@@ -251,7 +251,7 @@ func runGeminiRawCommand(
var process *exec.Cmd var process *exec.Cmd
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
process = exec.Command( process = exec.Command(
codexPowerShellPath(), windowsPowerShellPath(),
"-NoLogo", "-NoLogo",
"-NoProfile", "-NoProfile",
"-NonInteractive", "-NonInteractive",
@@ -35,7 +35,7 @@ func prepareGeminiTestChanges(
t *testing.T, t *testing.T,
fixture *geminiFixture, fixture *geminiFixture,
document *geminiDocument, document *geminiDocument,
) ([]*fileChange, *geminiRuntimePaths) { ) ([]*fileChange, *managedRuntimePaths) {
t.Helper() t.Helper()
paths, nodePath, err := preflightGeminiInstallation(fixture.config, document) paths, nodePath, err := preflightGeminiInstallation(fixture.config, document)
if err != nil { if err != nil {
@@ -110,7 +110,7 @@ func TestGeminiInstallRollsBackAllFiveFilesAfterSettingsFailure(t *testing.T) {
fixture.config.OrgID = "org-updated" fixture.config.OrgID = "org-updated"
fixture.config.Token = "token-updated" fixture.config.Token = "token-updated"
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if sameGeminiPath(destination, fixture.settingsPath) && if sameManagedPath(destination, fixture.settingsPath) &&
strings.HasSuffix(source, ".tmp") { strings.HasSuffix(source, ".tmp") {
return errors.New("injected Gemini settings failure") return errors.New("injected Gemini settings failure")
} }
@@ -139,13 +139,14 @@ func TestPreparedGeminiInstallRejectsConcurrentSettingsChange(t *testing.T) {
if err := os.WriteFile(fixture.settingsPath, concurrent, 0600); err != nil { if err := os.WriteFile(fixture.settingsPath, concurrent, 0600); err != nil {
t.Fatalf("write concurrent settings: %v", err) t.Fatalf("write concurrent settings: %v", err)
} }
err = writeGeminiChanges( err = writeManagedChanges(
changes, changes,
"Install Gemini CLI hooks", "Install Gemini CLI hooks",
nil, nil,
paths.runtimeRoot, paths.runtimeRoot,
paths.agentDirectory, paths.agentDirectory,
filepath.Dir(document.filePath), filepath.Dir(document.filePath),
"Gemini CLI configuration directory",
) )
if err == nil || !strings.Contains(err.Error(), "changed during installation") { if err == nil || !strings.Contains(err.Error(), "changed during installation") {
t.Fatalf("prepared install error = %v", err) t.Fatalf("prepared install error = %v", err)
@@ -204,7 +205,7 @@ func TestGeminiUninstallPreservesSettingsAfterCommitFailure(t *testing.T) {
t.Fatalf("read installed Gemini settings: %v", err) t.Fatalf("read installed Gemini settings: %v", err)
} }
fixture.plugin.rename = func(source, destination string) error { fixture.plugin.rename = func(source, destination string) error {
if sameGeminiPath(destination, fixture.settingsPath) && if sameManagedPath(destination, fixture.settingsPath) &&
strings.HasSuffix(source, ".tmp") { strings.HasSuffix(source, ".tmp") {
return errors.New("injected Gemini uninstall failure") return errors.New("injected Gemini uninstall failure")
} }
+3 -3
View File
@@ -41,7 +41,7 @@ func buildGrokCommand(runtimePath, scriptPath string) (string, error) {
) )
} }
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return codexWindowsCommand(runtimePath, scriptPath), nil return encodedWindowsCommand(runtimePath, scriptPath), nil
} }
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil
} }
@@ -85,10 +85,10 @@ func parseGrokLegacyWindowsCommand(command string) (string, string, bool) {
} }
func parseGrokCommand(command string) (string, string, bool) { func parseGrokCommand(command string) (string, string, bool) {
if runtimePath, scriptPath, ok := parseCodexPOSIXCommand(command); ok { if runtimePath, scriptPath, ok := parsePOSIXCommand(command); ok {
return runtimePath, scriptPath, true return runtimePath, scriptPath, true
} }
if runtimePath, scriptPath, ok := parseCodexWindowsCommand(command); ok { if runtimePath, scriptPath, ok := parseEncodedWindowsCommand(command); ok {
return runtimePath, scriptPath, true return runtimePath, scriptPath, true
} }
return parseGrokLegacyWindowsCommand(command) return parseGrokLegacyWindowsCommand(command)
+10
View File
@@ -88,6 +88,16 @@ func rejectDuplicateJSONCKeys(value *hujson.Value, label, location string) error
return nil return nil
} }
func decodeStrictJSONObject(source []byte, label string) (map[string]any, error) {
if !json.Valid(source) {
var value any
if err := json.Unmarshal(source, &value); err != nil {
return nil, fmt.Errorf("parse %s: %w", label, err)
}
}
return decodeJSONCObject(source, label, false)
}
func decodeJSONCObject(source []byte, label string, allowTrailingCommas bool) (map[string]any, error) { func decodeJSONCObject(source []byte, label string, allowTrailingCommas bool) (map[string]any, error) {
value, err := parseJSONC(source, label, allowTrailingCommas) value, err := parseJSONC(source, label, allowTrailingCommas)
if err != nil { if err != nil {
+3 -3
View File
@@ -39,7 +39,7 @@ func buildKimiCommand(runtimePath, scriptPath string) (string, error) {
return "", fmt.Errorf("kimi hook commands require absolute runtime and script paths") return "", fmt.Errorf("kimi hook commands require absolute runtime and script paths")
} }
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return codexWindowsCommand(runtimePath, scriptPath), nil return encodedWindowsCommand(runtimePath, scriptPath), nil
} }
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil
} }
@@ -80,10 +80,10 @@ func parseKimiLegacyWindowsCommand(command string) (string, string, bool) {
} }
func parseKimiCommand(command string) (string, string, bool) { func parseKimiCommand(command string) (string, string, bool) {
if runtimePath, scriptPath, ok := parseCodexPOSIXCommand(command); ok { if runtimePath, scriptPath, ok := parsePOSIXCommand(command); ok {
return runtimePath, scriptPath, true return runtimePath, scriptPath, true
} }
if runtimePath, scriptPath, ok := parseCodexWindowsCommand(command); ok { if runtimePath, scriptPath, ok := parseEncodedWindowsCommand(command); ok {
return runtimePath, scriptPath, true return runtimePath, scriptPath, true
} }
return parseKimiLegacyWindowsCommand(command) return parseKimiLegacyWindowsCommand(command)
-17
View File
@@ -3,7 +3,6 @@ package plugins
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@@ -18,18 +17,6 @@ func kiroConfigPaths() (string, string, error) {
filepath.Join(home, ".kiro", "hooks", "elydora-audit.json"), nil filepath.Join(home, ".kiro", "hooks", "elydora-audit.json"), nil
} }
func resolveNodeRuntime() (string, error) {
nodePath, err := exec.LookPath("node")
if err != nil {
return "", fmt.Errorf("resolve Node.js runtime: %w", err)
}
absolute, err := filepath.Abs(nodePath)
if err != nil {
return "", fmt.Errorf("resolve Node.js runtime path: %w", err)
}
return absolute, nil
}
func buildKiroCommand(runtimePath, scriptPath string) string { func buildKiroCommand(runtimePath, scriptPath string) string {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return quoteWindowsArgument(runtimePath) + " " + quoteWindowsArgument(scriptPath) return quoteWindowsArgument(runtimePath) + " " + quoteWindowsArgument(scriptPath)
@@ -37,10 +24,6 @@ func buildKiroCommand(runtimePath, scriptPath string) string {
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath) return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath)
} }
func quotePOSIXArgument(value string) string {
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
}
func quoteWindowsArgument(value string) string { func quoteWindowsArgument(value string) string {
if value != "" && !strings.ContainsAny(value, " \t\n\v\"") { if value != "" && !strings.ContainsAny(value, " \t\n\v\"") {
return value return value
+4 -4
View File
@@ -48,16 +48,16 @@ func buildKiroIdeCommand(runtimePath, scriptPath string) (string, error) {
) )
} }
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return codexWindowsCommand(runtimePath, scriptPath), nil return encodedWindowsCommand(runtimePath, scriptPath), nil
} }
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath), nil
} }
func parseKiroIdeCommand(command string) (string, string, bool) { func parseKiroIdeCommand(command string) (string, string, bool) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return parseCodexWindowsCommand(command) return parseEncodedWindowsCommand(command)
} }
return parseCodexPOSIXCommand(command) return parsePOSIXCommand(command)
} }
func kiroIdeRuntimeReferenceForCommand( func kiroIdeRuntimeReferenceForCommand(
@@ -66,7 +66,7 @@ func kiroIdeRuntimeReferenceForCommand(
) (*kiroIdeRuntimeReference, error) { ) (*kiroIdeRuntimeReference, error) {
runtimePath, scriptPath, ok := parseKiroIdeCommand(command) runtimePath, scriptPath, ok := parseKiroIdeCommand(command)
if !ok || !filepath.IsAbs(runtimePath) || !filepath.IsAbs(scriptPath) || if !ok || !filepath.IsAbs(runtimePath) || !filepath.IsAbs(scriptPath) ||
!isCodexNodeExecutable(runtimePath) || !isNodeExecutable(runtimePath) ||
!sameKiroIdeFileName(filepath.Base(scriptPath), scriptName) { !sameKiroIdeFileName(filepath.Base(scriptPath), scriptName) {
return nil, nil return nil, nil
} }
+1 -1
View File
@@ -331,7 +331,7 @@ func TestKiroIdeStatusRejectsStaleWindowsPowerShellLauncher(t *testing.T) {
command := action["command"].(string) command := action["command"].(string)
action["command"] = strings.Replace( action["command"] = strings.Replace(
command, command,
codexPowerShellPath(), windowsPowerShellPath(),
`C:\StaleWindows\System32\WindowsPowerShell\v1.0\powershell.exe`, `C:\StaleWindows\System32\WindowsPowerShell\v1.0\powershell.exe`,
1, 1,
) )
-1
View File
@@ -219,4 +219,3 @@ func (p *LettaPlugin) Status() (PluginStatus, error) {
status.Installed = status.HookConfigured && status.HookScriptExists status.Installed = status.HookConfigured && status.HookScriptExists
return status, nil return status, nil
} }
+489
View File
@@ -0,0 +1,489 @@
package plugins
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
)
type managedRuntimeContract struct {
agentID string
guardPath string
auditPath string
}
type managedRuntimePaths struct {
runtimeRoot string
agentDirectory string
configPath string
keyPath string
guardPath string
auditPath string
}
type managedScriptReference struct {
agentID string
scriptPath string
}
var managedRuntimeConfigFields = stringSet(
"org_id", "agent_id", "kid", "base_url", "token", "agent_name",
)
func sameManagedPath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if absolute, err := filepath.Abs(left); err == nil {
left = absolute
}
if absolute, err := filepath.Abs(right); err == nil {
right = absolute
}
return sameManagedName(left, right)
}
// sameManagedName compares identifiers case-insensitively on Windows.
func sameManagedName(left, right string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func sameManagedAgentID(left, right string) bool {
return sameManagedName(left, right)
}
func managedReferenceKey(agentID string) string {
if runtime.GOOS == "windows" {
return strings.ToLower(agentID)
}
return agentID
}
// resolveManagedScript accepts absolute ~/.elydora/<agent>/<scriptName> paths.
func resolveManagedScript(scriptPath, scriptName string) (*managedScriptReference, error) {
if !filepath.IsAbs(scriptPath) || !sameManagedName(filepath.Base(scriptPath), scriptName) {
return nil, nil
}
agentDirectory := filepath.Dir(scriptPath)
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
if !sameManagedPath(filepath.Dir(agentDirectory), runtimeRoot) {
return nil, nil
}
agentID := filepath.Base(agentDirectory)
if agentID == "" || agentID == "." || agentID == ".." {
return nil, nil
}
return &managedScriptReference{agentID: agentID, scriptPath: scriptPath}, nil
}
func validateManagedInstallConfig(config InstallConfig, agentKey, product string) error {
for _, field := range []struct{ name, value string }{
{"agent name", config.AgentName},
{"organization ID", config.OrgID},
{"agent ID", config.AgentID},
{"key ID", config.KID},
{"private key", config.PrivateKey},
{"base URL", config.BaseURL},
{"guard script path", config.GuardScriptPath},
} {
if strings.TrimSpace(field.value) == "" {
return fmt.Errorf("%s is required", field.name)
}
}
if config.AgentName != agentKey {
return fmt.Errorf("%s installation requires agent name %s", product, agentKey)
}
if config.Token != "" && strings.TrimSpace(config.Token) == "" {
return fmt.Errorf("token must contain a non-whitespace value when provided")
}
if err := validateManagedPrivateKey(config.PrivateKey); err != nil {
return err
}
return validateManagedBaseURL(config.BaseURL)
}
func resolveManagedRuntimePaths(
config InstallConfig,
guardScript, auditScript string,
) (*managedRuntimePaths, error) {
if config.AgentID == "" {
return nil, fmt.Errorf("agent ID is required")
}
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return nil, err
}
agentDirectory, err := ResolveAgentRuntimeDirectory(config.AgentID)
if err != nil {
return nil, err
}
paths := &managedRuntimePaths{
runtimeRoot: runtimeRoot, agentDirectory: agentDirectory,
configPath: filepath.Join(agentDirectory, "config.json"),
keyPath: filepath.Join(agentDirectory, "private.key"),
guardPath: filepath.Join(agentDirectory, guardScript),
auditPath: filepath.Join(agentDirectory, auditScript),
}
if !filepath.IsAbs(config.GuardScriptPath) ||
!sameManagedPath(config.GuardScriptPath, paths.guardPath) {
return nil, fmt.Errorf(
"guard runtime must use the managed Elydora agent directory: %s",
paths.guardPath,
)
}
if config.HookScript != "" && (!filepath.IsAbs(config.HookScript) ||
!sameManagedPath(config.HookScript, paths.auditPath)) {
return nil, fmt.Errorf(
"audit runtime must use the managed Elydora agent directory: %s",
paths.auditPath,
)
}
return paths, nil
}
func readManagedRuntimeConfig(path string) (map[string]any, bool, error) {
snapshot, err := readManagedFile(path, "Elydora runtime config", maxRuntimeConfigBytes)
if err != nil || snapshot == nil {
return nil, snapshot != nil, err
}
label := fmt.Sprintf("Elydora runtime config at %s", path)
config, err := decodeStrictJSONObject(snapshot.contents, label)
return config, true, err
}
func requireManagedRuntimeString(config map[string]any, field, configPath string) (string, error) {
value, ok := config[field].(string)
if !ok || strings.TrimSpace(value) == "" {
return "", fmt.Errorf("elydora runtime config %s is invalid: %s", field, configPath)
}
return value, nil
}
func validateManagedRuntimeConfig(
config map[string]any,
expectedAgentID, configPath, agentKey, product string,
) error {
fields := make([]string, 0, len(config))
for field := range config {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
if _, ok := managedRuntimeConfigFields[field]; !ok {
return fmt.Errorf(
`elydora runtime config has unsupported field %q: %s`,
field,
configPath,
)
}
}
for _, field := range []string{"org_id", "kid"} {
if _, err := requireManagedRuntimeString(config, field, configPath); err != nil {
return err
}
}
agentID, err := requireManagedRuntimeString(config, "agent_id", configPath)
if err != nil {
return err
}
if !sameManagedAgentID(agentID, expectedAgentID) || config["agent_name"] != agentKey {
return fmt.Errorf(
"elydora runtime identity does not match %s hooks: %s",
product,
configPath,
)
}
if _, exists := config["token"]; exists {
if _, err := requireManagedRuntimeString(config, "token", configPath); err != nil {
return err
}
}
baseURL, err := requireManagedRuntimeString(config, "base_url", configPath)
if err != nil {
return err
}
if err := validateManagedBaseURL(baseURL); err != nil {
return fmt.Errorf("elydora runtime config base URL is invalid at %s: %w", configPath, err)
}
return nil
}
type managedArtifact struct {
path string
label string
limit int64
}
func managedRuntimeArtifacts(agentDirectory, guardScript, auditScript string) []managedArtifact {
return []managedArtifact{
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
{filepath.Join(agentDirectory, guardScript), "Elydora guard runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, auditScript), "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "chain-state.json"), "Elydora chain state", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "status-cache.json"), "Elydora status cache", maxRuntimeConfigBytes},
{filepath.Join(agentDirectory, "error.log"), "Elydora error log", maxManagedSourceBytes},
}
}
// validateManagedRuntimeIdentity rejects an agent directory owned by another identity.
func validateManagedRuntimeIdentity(
agentDirectory, agentID, agentKey, product string,
extraArtifacts ...managedArtifact,
) error {
rootExists, err := managedPhysicalDirectoryExists(
filepath.Dir(agentDirectory), "Elydora runtime directory",
)
if err != nil || !rootExists {
return err
}
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory, "Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return err
}
configPath := filepath.Join(agentDirectory, "config.json")
config, configExists, err := readManagedRuntimeConfig(configPath)
if err != nil {
return err
}
artifactExists := false
artifacts := append(managedRuntimeArtifacts(agentDirectory, "guard.js", "hook.js"), extraArtifacts...)
for _, item := range artifacts {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil {
return inspectErr
}
artifactExists = artifactExists || exists
}
if !configExists {
if artifactExists {
return fmt.Errorf(
"elydora runtime identity cannot be verified without config.json: %s",
agentDirectory,
)
}
return nil
}
configuredID, ok := config["agent_id"].(string)
if !ok || config["agent_name"] != agentKey || !sameManagedAgentID(configuredID, agentID) {
return fmt.Errorf(
"elydora runtime config identity does not match %s agent %s: %s",
product,
agentID,
configPath,
)
}
return nil
}
type expectedRuntimeScripts func(agentID string, config map[string]any) (guard, audit []byte)
// managedRuntimeContractExists is the strict status check; nil expected accepts non-empty scripts.
func managedRuntimeContractExists(
contract managedRuntimeContract,
agentKey, product string,
expected expectedRuntimeScripts,
) (bool, error) {
runtimeRoot, err := AgentRuntimeRoot()
if err != nil {
return false, err
}
agentDirectory := filepath.Dir(contract.guardPath)
if !sameManagedPath(filepath.Dir(agentDirectory), runtimeRoot) ||
!sameManagedPath(contract.auditPath, filepath.Join(agentDirectory, "hook.js")) {
return false, nil
}
for _, directory := range []struct{ path, label string }{
{runtimeRoot, "Elydora runtime directory"},
{agentDirectory, "Elydora agent runtime directory"},
} {
exists, err := managedPhysicalDirectoryExists(directory.path, directory.label)
if err != nil || !exists {
return false, err
}
}
configPath := filepath.Join(agentDirectory, "config.json")
keyPath := filepath.Join(agentDirectory, "private.key")
config, configExists, err := readManagedRuntimeConfig(configPath)
if err != nil {
return false, err
}
key, err := readManagedFile(keyPath, "Elydora private key", maxProtectedSecretBytes)
if err != nil {
return false, err
}
guard, err := readManagedFile(contract.guardPath, "Elydora guard runtime", maxManagedSourceBytes)
if err != nil {
return false, err
}
audit, err := readManagedFile(contract.auditPath, "Elydora audit runtime", maxManagedSourceBytes)
if err != nil {
return false, err
}
if !configExists || key == nil || guard == nil || audit == nil {
return false, nil
}
if err := validateManagedRuntimeConfig(config, contract.agentID, configPath, agentKey, product); err != nil {
return false, err
}
if err := validateManagedPrivateKey(string(key.contents)); err != nil {
return false, fmt.Errorf("elydora private key at %s: %w", keyPath, err)
}
if expected == nil {
return len(guard.contents) > 0 && len(audit.contents) > 0, nil
}
expectedGuard, expectedAudit := expected(contract.agentID, config)
return string(guard.contents) == string(expectedGuard) &&
string(audit.contents) == string(expectedAudit), nil
}
func managedRuntimeFilesExist(
contracts []managedRuntimeContract,
agentKey, product string,
expected expectedRuntimeScripts,
) (bool, error) {
for _, contract := range contracts {
exists, err := managedRuntimeContractExists(contract, agentKey, product, expected)
if err != nil || exists {
return exists, err
}
}
return false, nil
}
// managedRuntimePresent is the presence-only status check; identity mismatch reports false.
func managedRuntimePresent(contract managedRuntimeContract, agentKey string) (bool, error) {
agentDirectory := filepath.Dir(contract.guardPath)
directoryExists, err := managedPhysicalDirectoryExists(
agentDirectory, "Elydora agent runtime directory",
)
if err != nil || !directoryExists {
return false, err
}
config, exists, err := readManagedRuntimeConfig(filepath.Join(agentDirectory, "config.json"))
if err != nil {
return false, err
}
configuredID, idOK := config["agent_id"].(string)
if !exists || !idOK || config["agent_name"] != agentKey ||
!sameManagedAgentID(configuredID, contract.agentID) {
return false, nil
}
for _, item := range []managedArtifact{
{contract.guardPath, "Elydora guard runtime", maxManagedSourceBytes},
{contract.auditPath, "Elydora audit runtime", maxManagedSourceBytes},
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", maxProtectedSecretBytes},
} {
exists, inspectErr := managedPhysicalFileExists(item.path, item.label, item.limit)
if inspectErr != nil || !exists {
return false, inspectErr
}
}
return true, nil
}
func managedRuntimeFilesPresent(contracts []managedRuntimeContract, agentKey string) (bool, error) {
for _, contract := range contracts {
present, err := managedRuntimePresent(contract, agentKey)
if err != nil || present {
return present, err
}
}
return false, nil
}
func buildManagedRuntimeConfig(config InstallConfig, agentKey string) ([]byte, error) {
value := map[string]any{
"org_id": config.OrgID, "agent_id": config.AgentID, "kid": config.KID,
"base_url": config.BaseURL, "agent_name": agentKey,
}
if config.Token != "" {
value["token"] = config.Token
}
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
}
encoded = append(encoded, '\n')
if len(encoded) > maxRuntimeConfigBytes {
return nil, fmt.Errorf(
"elydora runtime config exceeds %d bytes after JSON encoding",
maxRuntimeConfigBytes,
)
}
return encoded, nil
}
// managedRuntimeFileChanges prepares the guard, config, key, and audit runtime changes.
func managedRuntimeFileChanges(
config InstallConfig,
paths *managedRuntimePaths,
agentKey, guardScript, auditScript string,
) ([]*fileChange, error) {
runtimeConfig, err := buildManagedRuntimeConfig(config, agentKey)
if err != nil {
return nil, err
}
items := []struct {
path, label string
content []byte
mode os.FileMode
}{
{paths.guardPath, "Elydora guard runtime", []byte(guardScript), 0700},
{paths.configPath, "Elydora runtime config", runtimeConfig, 0600},
{paths.keyPath, "Elydora private key", []byte(config.PrivateKey), 0600},
{paths.auditPath, "Elydora audit runtime", []byte(auditScript), 0700},
}
changes := make([]*fileChange, 0, len(items)+1)
for _, item := range items {
change, err := prepareFileChange(item.path, item.label, item.content, item.mode)
if err != nil {
return nil, err
}
changes = append(changes, change)
}
return changes, nil
}
func hasFileChanges(changes []*fileChange) bool {
for _, change := range changes {
if change != nil {
return true
}
}
return false
}
// writeManagedChanges creates the runtime and settings directories, then commits.
func writeManagedChanges(
changes []*fileChange,
label string,
rename renameFunc,
runtimeRoot, agentDirectory, settingsDirectory, settingsLabel string,
) error {
if !hasFileChanges(changes) {
return nil
}
if agentDirectory != "" {
if err := EnsurePrivateDirectory(runtimeRoot); err != nil {
return err
}
if err := EnsurePrivateDirectory(agentDirectory); err != nil {
return err
}
}
if err := ensureManagedDirectory(settingsDirectory, settingsLabel); err != nil {
return err
}
return writeChanges(changes, label, rename)
}
+198
View File
@@ -0,0 +1,198 @@
package plugins
import (
"encoding/base64"
"encoding/binary"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"unicode/utf16"
)
const (
posixApostrophe = `'"'"'`
powerShellExitSuffix = "; exit $LASTEXITCODE"
powerShellEncodedFlags = " -NoLogo -NoProfile -NonInteractive -EncodedCommand "
)
func resolveNodeRuntime() (string, error) {
nodePath, err := exec.LookPath("node")
if err != nil {
return "", fmt.Errorf("resolve Node.js runtime: %w", err)
}
absolute, err := filepath.Abs(nodePath)
if err != nil {
return "", fmt.Errorf("resolve Node.js runtime path: %w", err)
}
return absolute, nil
}
func resolveAbsoluteNodeRuntime(product string) (string, error) {
nodePath, err := resolveNodeRuntime()
if err != nil {
return "", err
}
if !filepath.IsAbs(nodePath) || !isNodeExecutable(nodePath) {
return "", fmt.Errorf("%s hooks require an absolute Node.js executable path", product)
}
return nodePath, nil
}
func isNodeExecutable(path string) bool {
name := filepath.Base(path)
return name == "node" || strings.EqualFold(name, "node.exe")
}
func quotePOSIXArgument(value string) string {
return "'" + strings.ReplaceAll(value, "'", posixApostrophe) + "'"
}
func quotePowerShellArgument(value string) string {
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
}
func posixSource(runtimePath, scriptPath string) string {
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath)
}
func powerShellSource(runtimePath, scriptPath string) string {
return "& " + quotePowerShellArgument(runtimePath) + " " +
quotePowerShellArgument(scriptPath) + powerShellExitSuffix
}
func windowsPowerShellPath() string {
root := "C:\\Windows"
if runtime.GOOS == "windows" {
configured := os.Getenv("SystemRoot")
if filepath.IsAbs(configured) && !strings.ContainsAny(configured, "\"%\r\n") {
root = configured
}
}
return strings.TrimRight(root, `\/`) + `\System32\WindowsPowerShell\v1.0\powershell.exe`
}
func encodePowerShellSource(source string) string {
units := utf16.Encode([]rune(source))
raw := make([]byte, len(units)*2)
for index, value := range units {
binary.LittleEndian.PutUint16(raw[index*2:], value)
}
return base64.StdEncoding.EncodeToString(raw)
}
func decodePowerShellSource(encoded string) (string, bool) {
raw, err := base64.StdEncoding.DecodeString(encoded)
if err != nil || base64.StdEncoding.EncodeToString(raw) != encoded || len(raw)%2 != 0 {
return "", false
}
units := make([]uint16, len(raw)/2)
for index := range units {
units[index] = uint16(raw[index*2]) | uint16(raw[index*2+1])<<8
}
return string(utf16.Decode(units)), true
}
func encodedWindowsCommand(runtimePath, scriptPath string) string {
return `"` + windowsPowerShellPath() + `"` + powerShellEncodedFlags +
encodePowerShellSource(powerShellSource(runtimePath, scriptPath))
}
func readPOSIXArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); {
if strings.HasPrefix(command[index:], posixApostrophe) {
value.WriteByte('\'')
index += len(posixApostrophe)
continue
}
if command[index] == '\'' {
return value.String(), index + 1, true
}
value.WriteByte(command[index])
index++
}
return "", start, false
}
func parsePOSIXCommand(command string) (string, string, bool) {
runtimePath, next, ok := readPOSIXArgument(command, 0)
if !ok || next >= len(command) || command[next] != ' ' {
return "", "", false
}
scriptPath, end, ok := readPOSIXArgument(command, next+1)
return runtimePath, scriptPath, ok && end == len(command)
}
func readPowerShellArgument(command string, start int) (string, int, bool) {
if start >= len(command) || command[start] != '\'' {
return "", start, false
}
var value strings.Builder
for index := start + 1; index < len(command); index++ {
if command[index] != '\'' {
value.WriteByte(command[index])
continue
}
if index+1 < len(command) && command[index+1] == '\'' {
value.WriteByte('\'')
index++
continue
}
return value.String(), index + 1, true
}
return "", start, false
}
func parsePowerShellSource(source string) (string, string, bool) {
if !strings.HasPrefix(source, "& ") {
return "", "", false
}
runtimePath, next, ok := readPowerShellArgument(source, 2)
if !ok || next >= len(source) || source[next] != ' ' {
return "", "", false
}
scriptPath, end, ok := readPowerShellArgument(source, next+1)
return runtimePath, scriptPath, ok && source[end:] == powerShellExitSuffix
}
func windowsPathBase(value string) string {
index := strings.LastIndexAny(value, `\/`)
return value[index+1:]
}
func isAbsoluteWindowsPath(value string) bool {
return len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') ||
(value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' &&
(value[2] == '\\' || value[2] == '/') || strings.HasPrefix(value, `\\`)
}
func isPowerShellExecutable(value string) bool {
return isAbsoluteWindowsPath(value) &&
strings.EqualFold(windowsPathBase(value), "powershell.exe")
}
func parseEncodedWindowsCommand(command string) (string, string, bool) {
if !strings.HasPrefix(command, `"`) {
return "", "", false
}
closing := strings.IndexByte(command[1:], '"')
if closing < 0 {
return "", "", false
}
closing++
prefix := `"` + powerShellEncodedFlags
if !isPowerShellExecutable(command[1:closing]) || !strings.HasPrefix(command[closing:], prefix) {
return "", "", false
}
source, ok := decodePowerShellSource(command[closing+len(prefix):])
if !ok {
return "", "", false
}
return parsePowerShellSource(source)
}