fix(repo_mirror_push): resolve mirror credential from server env, not the payload (#49)
The mirror credential no longer has to ride the tool-call payload (which is persisted to transcript → claudewatcher → brain → gitea history). Adds remote_password_env: the name of a server-side env var the tool resolves at call time, so the secret stays in the server process. An env name that resolves to empty errors loudly rather than silently sending an empty password. Raw remote_password still works but the schema/description now mark it DISCOURAGED. Tests: password resolved from the env var (never in output); unset env var → ErrValidation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package tools_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -14,6 +15,45 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// #49: remote_password_env names a server-side env var; the secret is resolved
|
||||
// from the server environment and never rides the tool-call payload.
|
||||
func TestRepoMirrorPushTool_PasswordFromEnv(t *testing.T) {
|
||||
t.Setenv("TEST_MIRROR_PW", "env-secret")
|
||||
var gotPw string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var m map[string]any
|
||||
_ = json.Unmarshal(body, &m)
|
||||
gotPw, _ = m["remote_password"].(string)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte(`{"id":1,"remote_name":"m","remote_address":"a"}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
tool := tools.NewRepoMirrorPush(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"}))
|
||||
out, err := tool.Call(context.Background(), json.RawMessage(`{
|
||||
"owner":"mathias","name":"infra","action":"add",
|
||||
"remote_address":"https://github.com/mathias/infra.git",
|
||||
"remote_username":"mathias","remote_password_env":"TEST_MIRROR_PW"
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "env-secret", gotPw, "password must be resolved from the server env var")
|
||||
assert.NotContains(t, string(out), "env-secret")
|
||||
}
|
||||
|
||||
// remote_password_env pointing at an unset var must fail loudly, not silently
|
||||
// send an empty password.
|
||||
func TestRepoMirrorPushTool_EnvUnsetErrors(t *testing.T) {
|
||||
tool := tools.NewRepoMirrorPush(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"}))
|
||||
_, err := tool.Call(context.Background(), json.RawMessage(`{
|
||||
"owner":"mathias","name":"infra","action":"add",
|
||||
"remote_address":"https://github.com/x/y.git","remote_username":"u",
|
||||
"remote_password_env":"DEFINITELY_UNSET_MIRROR_VAR_XYZ"
|
||||
}`))
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, gitea.ErrValidation)
|
||||
}
|
||||
|
||||
func TestRepoMirrorPushTool_Add(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
|
||||
Reference in New Issue
Block a user