verify_signature compared the X-Gitea-Signature header against
"sha256=<hex>", which is GitHub's format. Gitea sends a bare 64-character
hex digest with no prefix, so a correctly-signed Gitea delivery could never
match and was rejected with 403 Invalid signature.
Confirmed by capturing a real Gitea delivery against a scratch listener:
SIG_PRESENT=True HAS_PREFIX=False SIG_LEN=64
Now strips an optional "sha256=" prefix before comparing, so both Gitea's
native format and GitHub-style senders verify. The HMAC computation and the
constant-time comparison are unchanged — an absent, empty, or incorrect
signature is still rejected exactly as before.
This was the second of two faults blocking the webhook gate; the first was
the repository webhook pointing at the host's public IP, which Gitea's
ALLOWED_HOST_LIST denied before any request left the process.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Complete CI system that lives outside TSHARPS branches:
- ci-webhook.py: HTTP server on port 9500, receives Gitea push webhooks
- ci-runner.sh: runs feature manifests, pytest, package checks (read-only)
- ci-notify.sh: sends results to Telegram CICD Pipeline topic (4706)
- ci-config.json: branch→worktree mapping, tokens, timeouts
- README.md: branch model, promotion workflow, switch-back plan
Same tests for ALL branches. No drift. Runner self-monitors for crashes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>