fix: enforce frozen agent hook blocking
This commit is contained in:
1 parent
a643ead036
commit
0234652963
2 files changed
+129
-2
No files matched your search
@@ -117,7 +117,7 @@ async function main() {
|
|||||||
if (age < CACHE_TTL_MS) {
|
if (age < CACHE_TTL_MS) {
|
||||||
if (cache.status === 'frozen') {
|
if (cache.status === 'frozen') {
|
||||||
process.stderr.write('Agent "' + AGENT_NAME + '" is frozen by Elydora. Tool execution blocked.\n');
|
process.stderr.write('Agent "' + AGENT_NAME + '" is frozen by Elydora. Tool execution blocked.\n');
|
||||||
process.exitCode = 1;
|
process.exitCode = 2;
|
||||||
}
|
}
|
||||||
return; // Cache is fresh — use cached result
|
return; // Cache is fresh — use cached result
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ async function main() {
|
|||||||
|
|
||||||
if (agentStatus === 'frozen') {
|
if (agentStatus === 'frozen') {
|
||||||
process.stderr.write('Agent "' + AGENT_NAME + '" is frozen by Elydora. Tool execution blocked.\n');
|
process.stderr.write('Agent "' + AGENT_NAME + '" is frozen by Elydora. Tool execution blocked.\n');
|
||||||
process.exitCode = 1;
|
process.exitCode = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const guardTestAgentID = "agent 1"
|
||||||
|
|
||||||
|
func writeGuardFixture(t *testing.T, status string) (string, string, *httptest.Server) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.EscapedPath() != "/v1/agents/agent%201" {
|
||||||
|
t.Errorf("request path = %q, want encoded agent path", r.URL.EscapedPath())
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"agent": map[string]string{"status": status},
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
homeDir := t.TempDir()
|
||||||
|
agentDir := filepath.Join(homeDir, ".elydora", guardTestAgentID)
|
||||||
|
if err := os.MkdirAll(agentDir, 0755); err != nil {
|
||||||
|
t.Fatalf("create agent directory: %v", err)
|
||||||
|
}
|
||||||
|
config, err := json.Marshal(map[string]string{
|
||||||
|
"agent_id": guardTestAgentID,
|
||||||
|
"base_url": server.URL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal config: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(agentDir, "config.json"), config, 0600); err != nil {
|
||||||
|
t.Fatalf("write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(homeDir, "guard.js")
|
||||||
|
if err := os.WriteFile(scriptPath, []byte(GenerateGuardScript("claudecode", guardTestAgentID)), 0755); err != nil {
|
||||||
|
t.Fatalf("write guard script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptPath, homeDir, server
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGuard(t *testing.T, scriptPath string, homeDir string) (int, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
nodeBinary, err := exec.LookPath("node")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("Node.js is required to execute generated hook scripts")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(nodeBinary, scriptPath)
|
||||||
|
cmd.Env = append(os.Environ(), "HOME="+homeDir, "USERPROFILE="+homeDir)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err == nil {
|
||||||
|
return 0, stderr.String()
|
||||||
|
}
|
||||||
|
exitError, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("run guard: %v", err)
|
||||||
|
}
|
||||||
|
return exitError.ExitCode(), stderr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGuardBlocksRemoteFrozenAgent(t *testing.T) {
|
||||||
|
scriptPath, homeDir, server := writeGuardFixture(t, "frozen")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
exitCode, stderr := runGuard(t, scriptPath, homeDir)
|
||||||
|
if exitCode != 2 {
|
||||||
|
t.Fatalf("exit code = %d, want 2; stderr = %q", exitCode, stderr)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr, "Tool execution blocked") {
|
||||||
|
t.Fatalf("stderr = %q, want blocking message", stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGuardAllowsActiveAgent(t *testing.T) {
|
||||||
|
scriptPath, homeDir, server := writeGuardFixture(t, "active")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
exitCode, stderr := runGuard(t, scriptPath, homeDir)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("exit code = %d, want 0; stderr = %q", exitCode, stderr)
|
||||||
|
}
|
||||||
|
if stderr != "" {
|
||||||
|
t.Fatalf("stderr = %q, want empty output", stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGuardBlocksCachedFrozenAgent(t *testing.T) {
|
||||||
|
scriptPath, homeDir, server := writeGuardFixture(t, "active")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
cache := map[string]any{
|
||||||
|
"status": "frozen",
|
||||||
|
"cached_at": time.Now().Add(time.Minute).UnixMilli(),
|
||||||
|
}
|
||||||
|
cacheJSON, err := json.Marshal(cache)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal cache: %v", err)
|
||||||
|
}
|
||||||
|
cachePath := filepath.Join(homeDir, ".elydora", guardTestAgentID, "status-cache.json")
|
||||||
|
if err := os.WriteFile(cachePath, cacheJSON, 0600); err != nil {
|
||||||
|
t.Fatalf("write cache: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode, stderr := runGuard(t, scriptPath, homeDir)
|
||||||
|
if exitCode != 2 {
|
||||||
|
t.Fatalf("exit code = %d, want 2; stderr = %q", exitCode, stderr)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr, "Tool execution blocked") {
|
||||||
|
t.Fatalf("stderr = %q, want blocking message", stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user