"""Replay-window enforcement for Linear webhook signature verification (AUTHZ-001).""" from __future__ import annotations import hashlib import hmac import json from datetime import UTC, datetime from agent import webapp _SECRET = "linear-signing-secret" def _sign(body: bytes) -> str: return hmac.new(_SECRET.encode("utf-8"), body, hashlib.sha256).hexdigest() def _now_ms() -> int: return int(datetime.now(UTC).timestamp() * 1000) def test_fresh_timestamp_accepted() -> None: body = json.dumps({"type": "Comment", "webhookTimestamp": _now_ms()}).encode() assert webapp.verify_linear_signature(body, _sign(body), _SECRET) is True def test_stale_timestamp_rejected() -> None: stale = _now_ms() - 10 * 60 * 1000 # 10 minutes old body = json.dumps({"type": "Comment", "webhookTimestamp": stale}).encode() # Signature is valid, but the timestamp is outside the freshness window. assert webapp.verify_linear_signature(body, _sign(body), _SECRET) is False def test_future_timestamp_rejected() -> None: future = _now_ms() + 10 * 60 * 1000 body = json.dumps({"type": "Comment", "webhookTimestamp": future}).encode() assert webapp.verify_linear_signature(body, _sign(body), _SECRET) is False def test_missing_timestamp_rejected() -> None: body = json.dumps({"type": "Comment"}).encode() assert webapp.verify_linear_signature(body, _sign(body), _SECRET) is False def test_non_numeric_timestamp_rejected() -> None: body = json.dumps({"type": "Comment", "webhookTimestamp": "not-a-number"}).encode() assert webapp.verify_linear_signature(body, _sign(body), _SECRET) is False def test_bad_signature_rejected_even_when_fresh() -> None: body = json.dumps({"type": "Comment", "webhookTimestamp": _now_ms()}).encode() assert webapp.verify_linear_signature(body, "deadbeef", _SECRET) is False