obs(selfhost): detect and alert on GitHub App JWT clock skew - #3861
Conversation
edge-us-01's system clock silently drifted ~3 minutes off true time
because its sole configured NTP source was dead (Reach: 0, no
redundant fallback), breaking GitHub App JWT auth ("Bad credentials")
for a window before anyone noticed.
Adds a gittensory_clock_skew_seconds gauge sampled from the Date
header of the GitHub App's own JWT-authenticated installation-token
mint response — no extra network round-trip, sampled at exactly the
cadence the vulnerable code path itself runs. Wires in Prometheus
warning/critical alert rules (60s/120s, both well under the 3-minute
drift actually observed) and a Grafana panel, plus docs/docker-compose
guidance on configuring redundant host NTP sources.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 6de2833 | Commit Preview URL Branch Preview URL |
Jul 06 2026, 07:43 PM |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3861 +/- ##
=======================================
Coverage 93.48% 93.48%
=======================================
Files 333 334 +1
Lines 33162 33172 +10
Branches 12128 12130 +2
=======================================
+ Hits 31001 31011 +10
Misses 1530 1530
Partials 631 631
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-06 19:54:36 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 7 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
chronyc sourcesshowedReach: 0the whole time, no redundant fallback), breaking GitHub App JWT auth ("Bad credentials") for a window before anyone noticed.gittensory_clock_skew_seconds, a gauge sampled from theDateresponse header of the GitHub App's own JWT-authenticated installation-token mint call (requestInstallationTokenWithJwt,src/github/app.ts) — no extra network round-trip, sampled at exactly the cadence the vulnerable code path itself runs. State lives in the newsrc/selfhost/clock-skew.ts, registered as a gauge insrc/server.tsalongside the other self-host-only host gauges.prometheus/rules/alerts.ymlwarning (60s) and critical (120s) drift alerts — both comfortably under the 3-minute drift actually observed, and tied directly to GitHub App JWTs' 60siatbackdate margin (createAppJwt).Clock Sync (NTP, #3811)row +Clock Skewpanel tografana/dashboards/gittensory.json.docs.self-hosting-operations.tsx) and a preamble comment indocker-compose.ymlpointing operators at configuring redundant host NTP sources — a single dead source is a silent single point of failure.Not included: correlating a JWT 401 rejection with elevated clock skew via
captureError(a "consider" item in the issue, not a hard requirement).captureError/Sentry is explicitly self-host-only tooling (only ever called fromsrc/server.ts/src/selfhost/*-queue.ts), andsrc/github/app.tsis shared cloud+self-host code with no existing precedent for importing it — the Prometheus alert rules are the primary, always-on alerting mechanism this issue asks for, so I kept the JWT retry path (already delicate, incident-driven code per #2453) untouched rather than adding a new cross-boundary import for a soft suggestion.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateAll run via
npm run test:ci(full local gate, green) plus a standalonenpm audit --audit-level=moderate(0 vulnerabilities). Also rannpm run selfhost:validate-observability(validates the new Prometheus rule YAML and Grafana dashboard JSON),docker compose config --quiet(validates the compose comment doesn't break parsing), andnpm run docs:drift-check/npm run selfhost:env-reference:check(no new env flags introduced — sampling is unconditional). Verified every new/changed line and branch insrc/selfhost/clock-skew.tsandsrc/github/app.ts's changed section is covered locally (v8 lcov branch report).Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails.This touches the GitHub App JWT-mint response path but only ADDS a passive header read (
recordClockSkewFromResponse) after the response is already received — no change to auth logic, retry behavior, or token caching, so no new auth-surface negative-path test is needed. The self-hosting-operations doc addition is a docs/text change (not an interactive UI surface), so no screenshot evidence applies.UI Evidence
N/A — this PR only adds a docs section (text/prose) and a Grafana JSON panel definition; no interactive app UI surface changed.
Notes
captureError. Left as a possible follow-up once/if a self-host-only wrapper around the App-JWT retry path exists.