repo_mirror_push takes remote_password (a GitHub PAT / mirror credential) as a normal input field. Every MCP tool call is persisted: claude.ai/terminal transcript → ~/.claude/projects → claudewatcher → brain/wiki → gitea history. So calling this tool with a real remote_passwordwrites the secret into searchable, long-lived storage — a leak that requires rotating the credential to remediate.
Surfaced running #19 (Gitea→GitHub mirror E2E): I could not use repo_mirror_push for the mirror-config step without leaking the PAT, so I fell back to the gitea REST API inside op run (PAT confined to the subprocess).
Options
Don't accept the raw secret. Accept an indirection instead — e.g. a remote_password_env naming an env var the server reads at call time, or a 1Password op:// reference the server resolves. The secret never transits the tool-call payload.
Server-side credential store. Look the mirror credential up server-side (keyed by remote host/owner) rather than taking it per-call.
At minimum, document loudly that remote_password must never be a real secret from a logged surface, and that the REST-in-op-run path is the safe route.
Option 1 (env/op:// reference) is the cleanest and matches the platform's secret-handling rule (secrets stay in the subprocess, never in argv/payload).
Acceptance
A caller can configure a push-mirror without the credential appearing in the tool-call payload/transcript.
Existing raw-password path either removed or gated behind a clear warning.
Refs
Surfaced during #19 (mirror E2E, 2026-07-04). Global secret-handling policy: "Secrets stay in the subprocess; never place a literal secret in a command's argv/payload."
## Problem
`repo_mirror_push` takes `remote_password` (a GitHub PAT / mirror credential) as a normal input field. Every MCP tool call is persisted: claude.ai/terminal transcript → `~/.claude/projects` → claudewatcher → brain/wiki → gitea history. So calling this tool with a real `remote_password` **writes the secret into searchable, long-lived storage** — a leak that requires rotating the credential to remediate.
Surfaced running #19 (Gitea→GitHub mirror E2E): I could not use `repo_mirror_push` for the mirror-config step without leaking the PAT, so I fell back to the gitea REST API inside `op run` (PAT confined to the subprocess).
## Options
1. **Don't accept the raw secret.** Accept an indirection instead — e.g. a `remote_password_env` naming an env var the server reads at call time, or a 1Password `op://` reference the server resolves. The secret never transits the tool-call payload.
2. **Server-side credential store.** Look the mirror credential up server-side (keyed by remote host/owner) rather than taking it per-call.
3. At minimum, **document loudly** that `remote_password` must never be a real secret from a logged surface, and that the REST-in-`op-run` path is the safe route.
Option 1 (env/`op://` reference) is the cleanest and matches the platform's secret-handling rule (secrets stay in the subprocess, never in argv/payload).
## Acceptance
- [ ] A caller can configure a push-mirror without the credential appearing in the tool-call payload/transcript.
- [ ] Existing raw-password path either removed or gated behind a clear warning.
## Refs
Surfaced during #19 (mirror E2E, 2026-07-04). Global secret-handling policy: "Secrets stay in the subprocess; never place a literal secret in a command's argv/payload."
Added remote_password_env to repo_mirror_push: the name of a server-side env var the tool resolves at call time (os.Getenv), so the mirror credential never appears in the tool-call payload (→ transcript → claudewatcher → brain → gitea history). An env name that resolves to empty errors loudly (ErrValidation) instead of silently sending an empty password. Raw remote_password still works but the schema now marks it DISCOURAGED and the descriptor steers callers to the env path.
Tests: TestRepoMirrorPushTool_PasswordFromEnv (secret resolved from the env var, never in output) + TestRepoMirrorPushTool_EnvUnsetErrors. task check green (exit 0, 0 FAIL — verified).
Infra follow-up (out of scope for this code change): for remote_password_env to resolve on the live server, the gitea-mcp deployment must actually carry the referenced env var (e.g. a GitHub mirror PAT injected via ESO into the pod env), then callers pass just the var name. Until that secret is wired into the deployment, the raw path or the REST-in-op run workaround remains. Filing/So noting — the code accepts the reference now; provisioning the pod env is the remaining infra step.
Fixed in v0.7.1.
Added **`remote_password_env`** to `repo_mirror_push`: the name of a server-side env var the tool resolves at call time (`os.Getenv`), so the mirror credential **never appears in the tool-call payload** (→ transcript → claudewatcher → brain → gitea history). An env name that resolves to empty **errors loudly** (`ErrValidation`) instead of silently sending an empty password. Raw `remote_password` still works but the schema now marks it **DISCOURAGED** and the descriptor steers callers to the env path.
Tests: `TestRepoMirrorPushTool_PasswordFromEnv` (secret resolved from the env var, never in output) + `TestRepoMirrorPushTool_EnvUnsetErrors`. `task check` green (exit 0, 0 FAIL — verified).
**Infra follow-up (out of scope for this code change):** for `remote_password_env` to resolve on the live server, the gitea-mcp deployment must actually carry the referenced env var (e.g. a GitHub mirror PAT injected via ESO into the pod env), then callers pass just the var *name*. Until that secret is wired into the deployment, the raw path or the REST-in-`op run` workaround remains. Filing/So noting — the code accepts the reference now; provisioning the pod env is the remaining infra step.
Closing #49.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
repo_mirror_pushtakesremote_password(a GitHub PAT / mirror credential) as a normal input field. Every MCP tool call is persisted: claude.ai/terminal transcript →~/.claude/projects→ claudewatcher → brain/wiki → gitea history. So calling this tool with a realremote_passwordwrites the secret into searchable, long-lived storage — a leak that requires rotating the credential to remediate.Surfaced running #19 (Gitea→GitHub mirror E2E): I could not use
repo_mirror_pushfor the mirror-config step without leaking the PAT, so I fell back to the gitea REST API insideop run(PAT confined to the subprocess).Options
remote_password_envnaming an env var the server reads at call time, or a 1Passwordop://reference the server resolves. The secret never transits the tool-call payload.remote_passwordmust never be a real secret from a logged surface, and that the REST-in-op-runpath is the safe route.Option 1 (env/
op://reference) is the cleanest and matches the platform's secret-handling rule (secrets stay in the subprocess, never in argv/payload).Acceptance
Refs
Surfaced during #19 (mirror E2E, 2026-07-04). Global secret-handling policy: "Secrets stay in the subprocess; never place a literal secret in a command's argv/payload."
Fixed in v0.7.1.
Added
remote_password_envtorepo_mirror_push: the name of a server-side env var the tool resolves at call time (os.Getenv), so the mirror credential never appears in the tool-call payload (→ transcript → claudewatcher → brain → gitea history). An env name that resolves to empty errors loudly (ErrValidation) instead of silently sending an empty password. Rawremote_passwordstill works but the schema now marks it DISCOURAGED and the descriptor steers callers to the env path.Tests:
TestRepoMirrorPushTool_PasswordFromEnv(secret resolved from the env var, never in output) +TestRepoMirrorPushTool_EnvUnsetErrors.task checkgreen (exit 0, 0 FAIL — verified).Infra follow-up (out of scope for this code change): for
remote_password_envto resolve on the live server, the gitea-mcp deployment must actually carry the referenced env var (e.g. a GitHub mirror PAT injected via ESO into the pod env), then callers pass just the var name. Until that secret is wired into the deployment, the raw path or the REST-in-op runworkaround remains. Filing/So noting — the code accepts the reference now; provisioning the pod env is the remaining infra step.Closing #49.