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
+7 -4
View File
@@ -693,13 +693,16 @@ Runs are recorded in `schedule_runs` and executed through `jobs`.
`{"url", "space_label"}`, both required. The URL must be exactly
`https://chat.googleapis.com/v1/spaces/{space}/messages?key=…&token=…`:
that scheme and host spelled so, no port or user info, the space id
letters, digits, `-` or `_`, and only the query parameters `key` and
`token`, each once and at least 16 characters. The space label is 1–100
letters, digits, `-` or `_`, and the raw query exactly
`key=…&token=…` (in that order, no empty segment, no second `?`, no
other parameter), each decoding to at least 16 characters of letters,
digits, `-`, `_`, `.`, `~` or `=`. The space label is 1–100
characters. A refused body is 400 `invalid_request` (not a JSON object
of the two fields) or `invalid_webhook`. Saving replaces any stored
webhook and clears its test outcome.
- `GET /api/v1/settings/chat-webhook` answers `{configured, space_label,
url_masked, last_test_at, last_test_status, updated_at}`; `url_masked`
- `GET /api/v1/settings/chat-webhook` answers `{space_label, url_masked,
last_test_at, last_test_status}`, so `url_masked` is `null` exactly when
no webhook is stored; `url_masked`
shows the space id and only the last four characters of the decoded
token (`…/spaces/AAQA…/messages?key=…&token=…ZxQ=`). Absent values are
`null`.