Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions internal/cli/agent_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cli

import (
"context"
"errors"
"io"
"os"
"os/exec"
"time"
)

type agentCommand func(context.Context, string, ...string) ([]byte, error)

const agentOutputLimit = 1 << 20

type agentOutput struct {
data []byte
full bool
}

func (w *agentOutput) Write(p []byte) (int, error) {
n := min(len(p), agentOutputLimit-len(w.data))
w.data = append(w.data, p[:n]...)
w.full = w.full || n != len(p)
return len(p), nil
}

// Native clients own their settings edits. No shell, raw diagnostics or inherited
// credentials; only platform paths and client configuration locations are passed.
func runAgentCommand(ctx context.Context, client string, args ...string) ([]byte, error) {
if client != "codex" && client != "claude" {
return nil, errors.New("unsupported MCP client")
}
process := exec.CommandContext(ctx, client, args...)
process.WaitDelay = 250 * time.Millisecond
process.Env = agentEnvironment()
var output agentOutput
process.Stdout, process.Stderr = &output, io.Discard
if err := process.Run(); err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
return nil, err
}
if output.full {
return nil, errors.New("client output exceeds limit")
}
return output.data, nil
}

func agentEnvironment() []string {
var env []string
for _, name := range []string{"PATH", "PATHEXT", "HOME", "USERPROFILE", "SystemRoot", "WINDIR", "LOCALAPPDATA", "APPDATA", "TEMP", "TMP", "TMPDIR", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME"} {
if value, ok := os.LookupEnv(name); ok {
env = append(env, name+"="+value)
}
}
return append(env, "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1", "DISABLE_AUTOUPDATER=1", "DISABLE_TELEMETRY=1", "DISABLE_ERROR_REPORTING=1")
}
101 changes: 101 additions & 0 deletions internal/cli/agent_command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cli

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)

func TestAgentCommandEnvironmentAndOutputAreBounded(t *testing.T) {
for _, name := range []string{"JEV_API_KEY", "JEVWISE_MCP_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "MCP_CLIENT_SECRET", "UNRELATED_SECRET"} {
t.Setenv(name, "private-secret")
}
t.Setenv("CODEX_HOME", "client-config-fixture")
env := strings.Join(agentEnvironment(), "\n")
if strings.Contains(env, "private") || !strings.Contains(env, "CODEX_HOME=client-config-fixture") || !strings.Contains(env, "DISABLE_AUTOUPDATER=1") {
t.Fatal("registration child inherited credentials or lost configuration location")
}
var output agentOutput
data := []byte(strings.Repeat("x", agentOutputLimit+1))
if n, err := output.Write(data); err != nil || n != len(data) || len(output.data) != agentOutputLimit || !output.full {
t.Fatal("client output was not bounded")
}
_, _ = output.Write(data)
if len(output.data) != agentOutputLimit {
t.Fatal("overflowing output kept growing")
}
if _, err := runAgentCommand(context.Background(), "unsupported-client"); err == nil {
t.Fatal("unsupported client executable was launched")
}
}

// Only the task-owned test binary, installed under a temporary client name,
// enters this fixture. No installed agent or user configuration is accessed.
func runAgentCommandFixture() int {
switch os.Args[2] {
case "timeout":
time.Sleep(time.Hour)
case "large":
fmt.Print(strings.Repeat("x", agentOutputLimit+1))
default:
for _, name := range []string{"JEV_API_KEY", "JEVWISE_MCP_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"} {
if os.Getenv(name) != "" {
return 2
}
}
if os.Getenv("CODEX_HOME") != "isolated-client-fixture" {
return 3
}
fmt.Fprintln(os.Stderr, "private-client-diagnostic")
fmt.Print("clean")
}
return 0
}

func TestAgentCommandRunsOnlyIsolatedFixtureWithDeadline(t *testing.T) {
binary, err := os.Executable()
if err != nil {
t.Fatal(err)
}
dir := t.TempDir()
name := "codex"
if runtime.GOOS == "windows" {
name += ".exe"
}
path := filepath.Join(dir, name)
if err := os.Link(binary, path); err != nil {
data, err := os.ReadFile(binary)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, data, 0755); err != nil {
t.Fatal(err)
}
}
t.Setenv("PATH", dir)
t.Setenv("CODEX_HOME", "isolated-client-fixture")
for _, key := range []string{"JEV_API_KEY", "JEVWISE_MCP_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"} {
t.Setenv(key, "private-fixture-key")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
data, err := runAgentCommand(ctx, "codex", "__jevwise-client-fixture", "clean")
if err != nil || string(data) != "clean" {
t.Fatal("child inherited credentials or diagnostics", err)
}
if data, err := runAgentCommand(ctx, "codex", "__jevwise-client-fixture", "large"); err == nil || data != nil {
t.Fatal("oversized subprocess output was accepted")
}
ctx, stop := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer stop()
started := time.Now()
if _, err := runAgentCommand(ctx, "codex", "__jevwise-client-fixture", "timeout"); !errors.Is(err, context.DeadlineExceeded) || time.Since(started) > 5*time.Second {
t.Fatal("owned client process ignored deadline", err)
}
}
3 changes: 3 additions & 0 deletions internal/cli/mcp_process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import (
// Only the task-owned test binary gets this synthetic child service. Production
// CLI registration, bootstrap, privacy, HTTP and cleanup remain real code paths.
func TestMain(m *testing.M) {
if len(os.Args) == 3 && os.Args[1] == "__jevwise-client-fixture" {
os.Exit(runAgentCommandFixture())
}
if len(os.Args) == 2 && os.Args[1] == daemon.ChildArgument && os.Getenv("JEVWISE_PROCESS_FIXTURE") == "1" {
os.Exit(runProcessFixture())
}
Expand Down
Loading