Skip to content
Closed
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
5 changes: 3 additions & 2 deletions internal/app/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func logHostOwnedPATDecisionOnce() {
hostOwnedPATDecisionOnce.Do(func() {
slog.Debug("runtime.host_owned_pat",
"hostOwned", authpkg.HostOwnsPATFlow(),
"agentCodeEnvPresent", os.Getenv(authpkg.AgentCodeEnv) != "",
"agentCodeEnvPresent", authpkg.AgentCodeEnvPresent(),
)
})
}
Expand Down Expand Up @@ -687,9 +687,10 @@ func resolveIdentityHeaders() map[string]string {
if sessionID == "" {
sessionID = os.Getenv(envRewindSessionID)
}
agentCode, _ := authpkg.AgentCodeFromEnv()
envHeaders := map[string]string{
"x-dingtalk-agent": os.Getenv(envDingtalkAgent),
"x-dingtalk-dws-agent-code": strings.TrimSpace(os.Getenv(authpkg.AgentCodeEnv)),
"x-dingtalk-dws-agent-code": agentCode,
"x-dingtalk-trace-id": os.Getenv(envDingtalkTraceID),
"x-dingtalk-session-id": sessionID,
"x-dingtalk-message-id": os.Getenv(envDingtalkMessageID),
Expand Down
11 changes: 11 additions & 0 deletions internal/app/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ func TestResolveIdentityHeadersForwardsAgentCode(t *testing.T) {
}
}

func TestResolveIdentityHeadersIgnoresReversedAgentCodeEnv(t *testing.T) {
setupRuntimeCommandTest(t)
t.Setenv(authpkg.AgentCodeEnv, "")
t.Setenv("DWS_DINGTALK_AGENTCODE", " compat ")

headers := resolveIdentityHeaders()
if got := headers["x-dingtalk-dws-agent-code"]; got != "" {
t.Fatalf("x-dingtalk-dws-agent-code = %q, want empty because reversed env is ignored", got)
}
}

func TestResolveIdentityHeadersSessionEnvPriority(t *testing.T) {
setupRuntimeCommandTest(t)
t.Setenv(envDingtalkSessionID, "ding-session")
Expand Down
37 changes: 28 additions & 9 deletions internal/auth/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,38 @@ import (
)

const (
// AgentCodeEnv is the sole per-spawn environment variable the host injects
// to declare "this process is driven by a third-party Agent host, render
// authorization UI yourselves".
// AgentCodeEnv is the primary per-spawn environment variable the host
// injects to declare "this process is driven by a third-party Agent host,
// render authorization UI yourselves".
AgentCodeEnv = "DINGTALK_DWS_AGENTCODE"
)

// AgentCodeFromEnv returns the effective host agent code and the env name that
// supplied it.
//
// Keep the public env surface intentionally single-spelled. The reversed
// DWS_DINGTALK_AGENTCODE draft name is not consumed, so host-owned PAT mode,
// gateway identity headers, and `pat chmod --agentCode` fallback all agree on
// the same stable signal: DINGTALK_DWS_AGENTCODE.
func AgentCodeFromEnv() (string, string) {
if value := strings.TrimSpace(os.Getenv(AgentCodeEnv)); value != "" {
return value, AgentCodeEnv
}
return "", ""
}

func AgentCodeEnvPresent() bool {
value, _ := AgentCodeFromEnv()
return value != ""
}

// HostOwnsPATFlow reports whether the current process is running under a
// third-party Agent host that will render the PAT authorization card
// itself. The sole trigger is AgentCodeEnv (DINGTALK_DWS_AGENTCODE) being
// non-empty. The CLI deliberately does not consult any other signal
// (DINGTALK_AGENT / DWS_CHANNEL / the wire claw-type header) for this
// decision so that server-side routing tags and the host-owned UI contract
// remain independent concerns.
// itself. The trigger is DINGTALK_DWS_AGENTCODE being non-empty. The CLI
// deliberately does not consult any other signal (DINGTALK_AGENT /
// DWS_CHANNEL / the wire claw-type header) for this decision so that
// server-side routing tags and the host-owned UI contract remain independent
// concerns.
func HostOwnsPATFlow() bool {
return strings.TrimSpace(os.Getenv(AgentCodeEnv)) != ""
return AgentCodeEnvPresent()
}
2 changes: 1 addition & 1 deletion internal/pat/browser_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func saveBrowserPolicy(configDir string, policy *BrowserPolicy) error {
}

func ResolveBrowserPolicy(configDir, explicitAgentCode string) (BrowserPolicySelection, error) {
agentCode, err := resolveAgentCode(explicitAgentCode, false)
agentCode, err := resolveAgentCode(explicitAgentCode)
if err != nil {
return BrowserPolicySelection{}, err
}
Expand Down
28 changes: 28 additions & 0 deletions internal/pat/browser_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package pat
import (
"bytes"
"encoding/json"
"strings"
"testing"
)

Expand Down Expand Up @@ -235,3 +236,30 @@ func TestBrowserPolicyCommand_NoAgentCodeWritesDefaultEvenWhenEnvSet(t *testing.
t.Fatalf("len(policy.Agents) = %d, want 0", got)
}
}

func TestBrowserPolicyCommand_RequiresEnabledFlag(t *testing.T) {
configDir := t.TempDir()
t.Setenv("DWS_CONFIG_DIR", configDir)

cmd := newBrowserPolicyCommand()
var stdout bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stdout)
cmd.SetArgs([]string{"--agentCode", "agt-command"})

err := cmd.Execute()
if err == nil {
t.Fatal("browser-policy Execute() error = nil, want missing --enabled error")
}
if !strings.Contains(err.Error(), "--enabled is required") {
t.Fatalf("browser-policy error = %q, want --enabled requirement", err.Error())
}

policy, loadErr := LoadBrowserPolicy(configDir)
if loadErr != nil {
t.Fatalf("LoadBrowserPolicy error = %v", loadErr)
}
if policy.Default != nil || len(policy.Agents) != 0 {
t.Fatalf("policy was modified despite missing --enabled: %#v", policy)
}
}
Loading
Loading