fix(gmail): redact embedded credentials in watch hook URL output - #960
Conversation
gmail watch status printed the configured hook URL verbatim in both text and JSON output. Any credentials embedded in that URL (basic-auth userinfo like https://user:pass@host, or secret query params such as ?token=...) were leaked in plaintext even without --show-secrets, defeating the existing hook bearer-token redaction. Redact the userinfo, query values, and fragment of the hook URL unless --show-secrets is set, keeping scheme/host/path visible. This mirrors the existing git remote URL redaction (redactGitURL) already used elsewhere in the CLI. Credential-free URLs are shown unchanged. Adds table tests asserting userinfo passwords and query tokens are redacted by default in both text and JSON output, revealed with --show-secrets, and that plain URLs are untouched.
|
Codex review: needs real behavior proof before merge. Reviewed August 6, 2026, 7:24 PM ET / 23:24 UTC. ClawSweeper reviewWhat this changesThe PR redacts Gmail watch hook URL userinfo, query values, and fragments in text and JSON status output unless Merge readiness⛔ Blocked until real behavior proof from a real setup is added - 5 items remain This focused security fix is still needed on current main, but it is not ready to merge: the proposed redactor deliberately leaves path-embedded webhook credentials visible, and the PR has only unit-test proof. Priority: P1 Review scores
Verification
How this fits togetherGmail watch stores an optional outbound webhook URL in local watch state and prints that state for CLI users and automation. That URL is also passed to the webhook sender, so status output must not expose credentials embedded in the configured destination. flowchart LR
A[Watch configuration] --> B[Stored watch state]
B --> C[Watch status command]
C --> D{Show secrets?}
D -->|No| E[Redacted status output]
D -->|Yes| F[Full status output]
B --> G[Webhook sender]
Before merge
Findings
Agent review detailsSecurityNeeds attention: The patch adds no dependency or execution surface, but its default renderer still exposes path-carried webhook secrets. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Use a conservative default representation that also conceals path-carried webhook credentials, while reserving the original URL for Do we have a high-confidence way to reproduce the issue? Yes—current main directly prints the stored hook URL, and the PR’s focused before/after test transcript exercises that same status renderer. Is this the best way to solve the issue? No—the userinfo/query/fragment approach is useful but preserving the full path leaves common opaque webhook secrets exposed; the default representation must protect that placement too. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 8fe3e7995d0b. LabelsLabel changes:
Label justifications:
EvidenceSecurity concerns:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
|
Landed in Maintainer fixes on top of the contributor commit:
Proof:
Thanks @bunlongheng for identifying and fixing this security boundary. |
Summary
gog gmail watch statusprints the configured webhook hook URL verbatim in both text and JSON output. Any credentials embedded in that URL are leaked in plaintext even without--show-secrets, which defeats the existing hook bearer-token redaction (landed in #136).Leaked credential classes:
https://user:pass@host/hook(Go'shttp.Clientuses this forAuthorization: Basic, so it is a real, functional webhook credential).https://host/hook?token=SECRET(Slack-style and many webhook providers put the secret in the query string; the CLI's own--tokenhelp even documents?token=).This is the same secret-leak class the project already guards against: the hook bearer token is redacted here, and git remote URLs are redacted via
redactGitURLininternal/backup/git.go. The hook URL was simply missed.Fix
Add
redactHookURLand apply it inwriteWatchState(text + JSON paths) unless--show-secretsis passed. It strips userinfo, query values, and fragment while keeping scheme/host/path visible so the destination stays recognizable. Credential-free URLs are returned unchanged. This mirrors the existingredactGitURLbehavior for consistency.--show-secretsstill reveals the full URL (unchanged escape hatch).Reproduction / Evidence
Test added to the existing
internal/cmd/gmail_watch_redact_test.go.Before the fix (source reverted to
main, new test kept) - credentials leak:After the fix - all pass:
Verification
Why it matters
gogcliis designed to be driven by agents and scripts;watch statusoutput routinely lands in logs, transcripts, and shared terminals. A hook URL with embedded credentials should be treated exactly like the bearer token it sits next to.