Files
Elydora-Go-SDK/cmd/elydora/plugins/claudecode_ownership.go
T
雷电芽衣 a7fc75bf6b 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
2026-09-03 01:42:27 -04:00

258 lines
6.5 KiB
Go

package plugins
import (
"path/filepath"
"sort"
"strings"
)
type claudeRuntimeReference = managedScriptReference
func claudeRuntimeReferenceForScript(
scriptPath string,
scriptName string,
) (*claudeRuntimeReference, error) {
return resolveManagedScript(scriptPath, scriptName)
}
func claudeLegacyReference(
command string,
scriptName string,
) (*claudeRuntimeReference, error) {
if !strings.HasPrefix(command, "node ") || strings.ContainsAny(command, "\r\n") {
return nil, nil
}
scriptPath := strings.TrimPrefix(command, "node ")
if scriptPath == "" || strings.TrimSpace(scriptPath) != scriptPath {
return nil, nil
}
return claudeRuntimeReferenceForScript(scriptPath, scriptName)
}
func managedClaudeReference(
handler map[string]any,
scriptName string,
statusMessage string,
includeLegacy bool,
) (*claudeRuntimeReference, error) {
if len(handler) == 5 && handler["type"] == "command" &&
handler["timeout"] == claudeHookTimeout &&
handler["statusMessage"] == statusMessage {
nodePath, nodeOK := handler["command"].(string)
args, argsOK := handler["args"].([]any)
if nodeOK && filepath.IsAbs(nodePath) && isNodeExecutable(nodePath) &&
argsOK && len(args) == 1 {
if scriptPath, ok := args[0].(string); ok {
return claudeRuntimeReferenceForScript(scriptPath, scriptName)
}
}
}
if includeLegacy && len(handler) == 2 && handler["type"] == "command" {
if command, ok := handler["command"].(string); ok {
return claudeLegacyReference(command, scriptName)
}
}
return nil, nil
}
func buildClaudeGroup(nodePath, scriptPath, statusMessage string) claudeGroup {
return claudeGroup{
object: map[string]any{},
handlers: []map[string]any{{
"type": "command", "command": nodePath, "args": []any{scriptPath},
"timeout": claudeHookTimeout, "statusMessage": statusMessage,
}},
}
}
func exactManagedClaudeGroup(group claudeGroup) bool {
_, hasHooks := group.object["hooks"]
return len(group.object) == 1 && hasHooks
}
func removeManagedClaudeGroups(
groups []claudeGroup,
scriptName string,
statusMessage string,
agentID string,
) ([]claudeGroup, bool, error) {
result := make([]claudeGroup, 0, len(groups))
removed := false
for _, group := range groups {
kept := make([]map[string]any, 0, len(group.handlers))
for _, handler := range group.handlers {
reference, err := managedClaudeReference(
handler,
scriptName,
statusMessage,
true,
)
if err != nil {
return nil, false, err
}
owned := reference != nil &&
(agentID == "" || sameManagedAgentID(reference.agentID, agentID))
if owned {
removed = true
continue
}
kept = append(kept, handler)
}
if len(kept) > 0 || !exactManagedClaudeGroup(group) {
object := cloneClaudeObject(group.object)
object["hooks"] = group.object["hooks"]
result = append(result, claudeGroup{object: object, handlers: kept})
}
}
return result, removed, nil
}
func removeManagedClaudeHooks(hooks claudeHooks, agentID string) (claudeHooks, error) {
result := cloneClaudeHooks(hooks)
for _, contract := range []struct{ event, script, status string }{
{"PreToolUse", claudeGuardScript, claudeGuardStatusMessage},
{"PostToolUse", claudeAuditScript, claudeAuditStatusMessage},
{"PostToolUseFailure", claudeAuditScript, claudeAuditStatusMessage},
} {
groups, removed, err := removeManagedClaudeGroups(
result[contract.event],
contract.script,
contract.status,
agentID,
)
if err != nil {
return nil, err
}
if !removed {
continue
}
if len(groups) == 0 {
delete(result, contract.event)
} else {
result[contract.event] = groups
}
}
return result, nil
}
func managedClaudeEvent(event string) (string, string, bool) {
switch event {
case "PreToolUse":
return claudeGuardScript, claudeGuardStatusMessage, true
case "PostToolUse", "PostToolUseFailure":
return claudeAuditScript, claudeAuditStatusMessage, true
default:
return "", "", false
}
}
func entirelyManagedClaudeDocument(document *claudeDocument) bool {
if !document.exists || len(document.root) != 1 || len(document.hooks) == 0 {
return false
}
if _, hasHooks := document.root["hooks"]; !hasHooks {
return false
}
handlerCount := 0
for event, groups := range document.hooks {
scriptName, statusMessage, managedEvent := managedClaudeEvent(event)
if !managedEvent || len(groups) == 0 {
return false
}
for _, group := range groups {
if !exactManagedClaudeGroup(group) || len(group.handlers) == 0 {
return false
}
for _, handler := range group.handlers {
reference, err := managedClaudeReference(
handler,
scriptName,
statusMessage,
true,
)
if err != nil || reference == nil {
return false
}
handlerCount++
}
}
}
return handlerCount > 0
}
func claudeReferencesForEvent(
groups []claudeGroup,
scriptName string,
statusMessage string,
) (map[string][]claudeRuntimeReference, error) {
result := map[string][]claudeRuntimeReference{}
for _, group := range groups {
if !exactManagedClaudeGroup(group) {
continue
}
for _, handler := range group.handlers {
reference, err := managedClaudeReference(
handler,
scriptName,
statusMessage,
false,
)
if err != nil {
return nil, err
}
if reference != nil {
key := managedReferenceKey(reference.agentID)
result[key] = append(result[key], *reference)
}
}
}
return result, nil
}
func claudeRuntimeContracts(hooks claudeHooks) ([]claudeRuntimeContract, error) {
guards, err := claudeReferencesForEvent(
hooks["PreToolUse"],
claudeGuardScript,
claudeGuardStatusMessage,
)
if err != nil {
return nil, err
}
successes, err := claudeReferencesForEvent(
hooks["PostToolUse"],
claudeAuditScript,
claudeAuditStatusMessage,
)
if err != nil {
return nil, err
}
failures, err := claudeReferencesForEvent(
hooks["PostToolUseFailure"],
claudeAuditScript,
claudeAuditStatusMessage,
)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(guards))
for key := range guards {
keys = append(keys, key)
}
sort.Strings(keys)
contracts := make([]claudeRuntimeContract, 0, len(keys))
for _, key := range keys {
guard := guards[key]
success := successes[key]
failure := failures[key]
if len(guard) != 1 || len(success) != 1 || len(failure) != 1 ||
!sameManagedPath(success[0].scriptPath, failure[0].scriptPath) {
continue
}
contracts = append(contracts, claudeRuntimeContract{
agentID: guard[0].agentID, guardPath: guard[0].scriptPath,
auditPath: success[0].scriptPath,
})
}
return contracts, nil
}