telemetry: synthetic:true on canary refusals (keyed, not a bare flag)
All checks were successful
check / gate (push) Successful in 25s
canary / probe (push) Successful in 7s

The canary deliberately sends forged tokens every 10 min; those refusal
rows read as attacks. It now sends X-Windy-Synthetic carrying a shared
secret (Gitea repo secret CANARY_SYNTHETIC_KEY = WINDYGIT_SYNTHETIC_KEY in
Veron .env); the API marks the row synthetic only on a constant-time
match, so an attacker cannot label their own refusals synthetic to hide.
synthetic is declared on forge.auth.failed (Telemetry Boss, UPDATE 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 11:54:56 -04:00
parent 90643fe48e
commit 1c3b5b0638
6 changed files with 54 additions and 6 deletions

View File

@@ -71,6 +71,9 @@ class Settings(BaseSettings):
# root-only /etc/windygit/telemetry.env on Veron, never in the repo.
windygit_telemetry_token: str = ""
telemetry_ingest_url: str = "https://admin.windyword.ai/v1/events"
# Shared with our canary (Gitea repo secret CANARY_SYNTHETIC_KEY). A request
# carrying it in X-Windy-Synthetic is our own tooling -> synthetic:true.
windygit_synthetic_key: str = ""
# Internal callers (the Cloud portal calling /internal/*). A first-class
# caller class, not a bypass: unset means service calls are REFUSED.

View File

@@ -27,7 +27,7 @@ from api.app.providers.registry import (
R2Provider,
)
from api.app.routes import health, repos, webhooks
from api.app.telemetry import Telemetry, caller_class
from api.app.telemetry import Telemetry, caller_class, is_synthetic
logging.basicConfig(
level=logging.INFO,
@@ -47,9 +47,7 @@ def _refuse_kit_zero(settings) -> None:
if not settings.is_production:
return
try:
local_ips = {
info[4][0] for info in socket.getaddrinfo(socket.gethostname(), None)
}
local_ips = {info[4][0] for info in socket.getaddrinfo(socket.gethostname(), None)}
except socket.gaierror:
return
if settings.kit0_host in local_ips:
@@ -169,6 +167,11 @@ async def _repair_pointer_handler(request: Request, exc: RepairPointer) -> JSONR
caller=caller_class(request.headers),
route=getattr(route, "path", None),
upstream_status=getattr(exc, "upstream_status", None),
synthetic=is_synthetic(
request.headers, request.app.state.settings.windygit_synthetic_key
)
if hasattr(request.app.state, "settings")
else False,
)
return JSONResponse(status_code=exc.status_code, content=exc.detail)

View File

@@ -65,6 +65,14 @@ def _iso(epoch: float) -> str:
return datetime.fromtimestamp(epoch, UTC).isoformat().replace("+00:00", "Z")
def is_synthetic(headers, key: str) -> bool:
"""Our own tooling proves itself with the shared key; a bare header proves nothing."""
import hmac
presented = headers.get("x-windy-synthetic") or ""
return bool(key) and bool(presented) and hmac.compare_digest(presented, key)
def caller_class(headers) -> str:
"""Declared values: anonymous_human | anonymous_agent | unknown."""
from api.app.ept import looks_like_ept
@@ -145,10 +153,16 @@ class Telemetry:
caller: str,
route: str | None = None,
upstream_status: int | None = None,
synthetic: bool = False,
) -> None:
if code not in AUTH_CODES:
return
meta: dict = {"code": code, "http_status": int(http_status), "caller": caller}
meta: dict = {
"code": code,
"http_status": int(http_status),
"caller": caller,
"synthetic": bool(synthetic),
}
if route:
meta["route"] = route
if upstream_status is not None: