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
2 changes: 1 addition & 1 deletion internal/execution/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (b *Broker) handler(mapPath func(*http.Request) (string, error), authorize
func (b *Broker) cloudflareHandler(providerBase string) http.HandlerFunc {
return func(response http.ResponseWriter, request *http.Request) {
if !strings.HasPrefix(request.URL.Path, "/client/v4/") {
http.Error(response, "Wrangler request is outside Cloudflare API v4", http.StatusBadRequest)
http.Error(response, "request is outside Cloudflare API v4", http.StatusBadRequest)
return
}
path := strings.TrimPrefix(request.URL.RequestURI(), "/client/v4")
Expand Down
40 changes: 34 additions & 6 deletions internal/execution/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -220,23 +221,50 @@ func TestNativeResourceToolRejectsUnadvertisedExecutables(t *testing.T) {
}
}

func TestNativeCommandsDescribeWrappedWranglerExecutables(t *testing.T) {
func TestNativeCommandsDescribeWrappedCloudflareExecutables(t *testing.T) {
t.Parallel()
commands := NativeCommands([]catalog.ToolIntegration{
{ID: "git", Executables: []string{"git"}, Protocol: "git-smart-http"},
{ID: "wrangler", Executables: []string{"wrangler", "npx", "pnpm"}, Protocol: "cloudflare-api-base"},
{ID: "cf", Executables: []string{"cf", "npx", "pnpm"}, Protocol: "cloudflare-api-base"},
})
if got := strings.Join(commands, ", "); got != "git, wrangler, npx wrangler, pnpm wrangler" {
if got := strings.Join(commands, ", "); got != "git, wrangler, npx wrangler, pnpm wrangler, cf, npx cf, pnpm cf" {
t.Fatalf("commands = %q", got)
}
}

func TestNativeCloudflareCommandSelectsOnlyAdvertisedPackage(t *testing.T) {
bin := t.TempDir()
for _, name := range []string{"npx", "pnpm"} {
if err := os.WriteFile(filepath.Join(bin, name), nil, 0o755); err != nil {
t.Fatal(err)
}
}
t.Setenv("PATH", bin)
integrations := []catalog.ToolIntegration{
{ID: "wrangler", Executables: []string{"wrangler", "npx", "pnpm"}, Protocol: "cloudflare-api-base"},
{ID: "cf", Executables: []string{"cf", "npx", "pnpm"}, Protocol: "cloudflare-api-base"},
}
for _, command := range [][]string{{"npx", "cf", "zones", "list"}, {"pnpm", "cf", "zones", "list"}} {
integration, _, err := selectIntegration(integrations, command)
if err != nil || integration.ID != "cf" {
t.Fatalf("command %v selected %q: %v", command, integration.ID, err)
}
}
_, _, err := selectIntegration(integrations, []string{"npx", "unrelated"})
if err == nil || !strings.Contains(err.Error(), "does not advertise") {
t.Fatalf("unadvertised package error = %v", err)
}
}

func TestCloudflareNativeToolEnvironmentRemovesProviderCredentials(t *testing.T) {
t.Parallel()
values := cleanEnvironment([]string{"PATH=/bin", "CLOUDFLARE_API_TOKEN=secret", "CF_API_KEY=secret", "SAFE=value"}, providerCredentialNames("wrangler"))
joined := strings.Join(values, "\n")
if strings.Contains(joined, "secret") || !strings.Contains(joined, "SAFE=value") {
t.Fatalf("environment = %q", joined)
for _, integration := range []string{"wrangler", "cf"} {
values := cleanEnvironment([]string{"PATH=/bin", "CLOUDFLARE_API_TOKEN=secret", "CF_API_KEY=secret", "SAFE=value"}, providerCredentialNames(integration))
joined := strings.Join(values, "\n")
if strings.Contains(joined, "secret") || !strings.Contains(joined, "SAFE=value") {
t.Fatalf("%s environment = %q", integration, joined)
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions internal/execution/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ func NativeCommands(integrations []catalog.ToolIntegration) []string {
}

func nativeCommandPrefix(integration catalog.ToolIntegration, executable string) []string {
if (executable == "npx" || executable == "pnpm") && integration.ID == "wrangler" {
return []string{executable, "wrangler"}
if (executable == "npx" || executable == "pnpm") && (integration.ID == "wrangler" || integration.ID == "cf") {
return []string{executable, integration.ID}
}
return []string{executable}
}
Expand Down Expand Up @@ -272,7 +272,7 @@ func setEnvironment(values []string, pairs ...string) []string {

func providerCredentialNames(id string) []string {
switch id {
case "wrangler":
case "wrangler", "cf":
return []string{"CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY", "CLOUDFLARE_EMAIL", "CF_API_TOKEN", "CF_API_KEY", "CF_EMAIL"}
case "gh":
return []string{"GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN", "GH_HOST", "GH_CONFIG_DIR", "SSL_CERT_FILE"}
Expand Down
8 changes: 4 additions & 4 deletions specs/cli.feature
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,11 @@ Feature: Realmroot Toolbox command line
And local commits derive stable name and email from the immutable Agent username without changing global Git configuration

@journey:cloudflare-native-tool @entrypoint:exec
Scenario: Use Wrangler as the stable Agent
Given Cloudflare advertises a Wrangler integration
Scenario: Use Cloudflare CLIs as the stable Agent
Given Cloudflare advertises Wrangler and cf integrations
And the Agent has approved Cloudflare authority
When it runs Wrangler through "realmroot exec cloudflare"
Then Wrangler API traffic is routed through the Cloudflare Resource Server
When it runs Wrangler or cf through "realmroot exec cloudflare"
Then Cloudflare API traffic is routed through the Cloudflare Resource Server
And existing Cloudflare credentials are removed from the child environment
And Cloudflare asset-upload credentials remain process-local and are accepted only for their matching upload session

Expand Down
Loading