fix(repo_mirror_push): resolve mirror credential from server env, not the payload (#49)
CD / Lint / Test / Vet (push) Successful in 7s
CD / Build & Import (push) Successful in 21s
CD / Deploy via GitOps (push) Has been skipped

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:
2026-07-04 09:00:07 +02:00
co-authored by Claude Opus 4.8
parent 6d344c74a8
commit 64176fe6d7
2 changed files with 75 additions and 12 deletions
+40
View File
@@ -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)