Tighten Google Chat webhook validation and settings response
CI / ci (push) Successful in 6m36s
CI / ci (pull_request) Successful in 5m55s

Restrict the decoded key and token to a URL-safe ASCII alphabet so the
16-character minimum and the four-character masked tail measure the same
thing; a token of a few multi-byte characters could otherwise be shown in
full (MQ-165-01).

Validate the raw query as exactly key=…&token=…, refusing empty
segments, a second "?", reordered, extra or repeated parameters
(MQ-165-02).

Drop configured and updated_at from the settings response so it is the
four-field contract: space_label, url_masked, last_test_at,
last_test_status; url_masked is null exactly when nothing is stored
(MQ-165-03).

Task: 919e9422-3a41-4f1f-8967-77da17f5f9f3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 14:15:23 -04:00
co-authored by Claude Opus 5.5
parent 0e88ce811e
commit 38b9e5a06e
6 changed files with 55 additions and 38 deletions
+2 -2
View File
@@ -172,7 +172,7 @@ func TestPutGetStoresSealedWebhook(t *testing.T) {
svc, st := newTestService(t, newFakeChat(t))
got, err := svc.Get(ctx)
if err != nil || got.Configured || got.SpaceLabel != nil || got.URLMasked != nil || got.LastTestAt != nil {
if err != nil || got.SpaceLabel != nil || got.URLMasked != nil || got.LastTestAt != nil {
t.Fatalf("Get unconfigured = %+v, %v", got, err)
}
@@ -180,7 +180,7 @@ func TestPutGetStoresSealedWebhook(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !put.Configured || *put.SpaceLabel != "Family dinner" || *put.URLMasked != "https://chat.googleapis.com/v1/spaces/"+testSpace+"/messages?key=…&token=…ZxQ=" || put.LastTestAt != nil || put.LastTestStatus != nil {
if *put.SpaceLabel != "Family dinner" || *put.URLMasked != "https://chat.googleapis.com/v1/spaces/"+testSpace+"/messages?key=…&token=…ZxQ=" || put.LastTestAt != nil || put.LastTestStatus != nil {
t.Errorf("Put = %+v", put)
}
got, err = svc.Get(ctx)