hermes-agent/gateway/platforms
Ben 3fc7b624d8 feat(cron,gateway): NAS-JWT fire verifier + /api/cron/fire webhook (Chronos)
Phase 4E (E.1 + E.2). The inbound side of Chronos: NAS POSTs the agent when a
one-shot fires; the agent verifies a NAS-minted JWT and runs the job.

E.1 — plugins/cron/chronos/verify.py:
- verify_nas_fire_token(token, expected_audience, jwks_or_key, issuer): verifies
  signature against the NAS JWKS (RS/ES family; symmetric rejected), aud == this
  agent, exp/nbf, iss, and purpose == "cron_fire" (so a general agent JWT can't
  be replayed against the fire endpoint). Returns claims or None; never raises.
  Crypto delegated to PyJWT[crypto] (already a declared dep) — no hand-rolled
  JWT, no new dependency. No key configured → refuse (never unsigned-decode a
  security boundary).
- get_fire_verifier(): pluggable indirection so the DQ-4 escape hatch
  (direct per-job cron-key) can swap in with no handler change.

E.2 — gateway/platforms/api_server.py:
- POST /api/cron/fire (registered only when _CRON_AVAILABLE). Authenticated by
  the NAS-JWT via get_fire_verifier() — NOT API_SERVER_KEY (NAS holds no API
  key; this is the only inbound that triggers remote job execution, so it gets
  its own purpose-scoped check). Verifier args come from cron.chronos.* config.
  401 on bad/missing/forged token. 400 on missing job_id. On success: 202 +
  fire_due runs in the background (so a long agent turn never trips NAS's HTTP
  timeout); the store CAS claim inside fire_due de-dupes a scheduler retry.

Tests:
- test_chronos_verify (11): REAL RS256 signing — valid→claims, wrong-aud,
  missing/wrong purpose, expired, wrong-iss, tampered-signature (attacker key),
  no-key-refuse, empty-token, JWKS-URL key resolution, get_fire_verifier.
- test_cron_fire_webhook (5): valid→202+fire, invalid→401+no-fire, missing
  token→401, missing job_id→400, and fire path does NOT require API_SERVER_KEY.
api_server regression suites (214) green.

E.3 (NAS endpoints) is a separate cross-repo PR; the wire contract lands next
(docs/chronos-managed-cron-contract.md).
2026-06-18 14:46:33 +10:00
..
qqbot fix(qqbot): stop 100% CPU spin when WebSocket is closed but not None (#31193, #31771) (#40574) 2026-06-06 18:44:44 -07:00
__init__.py perf(gateway): defer QQAdapter and YuanbaoAdapter imports via PEP 562 (#22790) 2026-05-09 13:17:48 -07:00
_http_client_limits.py fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) 2026-05-02 02:23:37 -07:00
ADDING_A_PLATFORM.md feat(whatsapp): add WhatsApp Business Cloud API adapter 2026-05-23 01:07:01 -04:00
api_server.py feat(cron,gateway): NAS-JWT fire verifier + /api/cron/fire webhook (Chronos) 2026-06-18 14:46:33 +10:00
base.py fix(mattermost): harden delivery hygiene 2026-06-16 06:34:54 -07:00
bluebubbles.py refactor(bluebubbles): simplify mention-gating helpers 2026-06-01 18:52:05 -07:00
dingtalk.py fix(dingtalk): finalize open streaming cards before disconnect 2026-05-23 20:48:56 -07:00
email.py fix(gateway): accept metadata kwarg in WhatsApp/email send_image 2026-06-16 06:23:53 -07:00
feishu.py feat(gateway): render terminal tool calls as native bash code blocks on markdown platforms (#41215) 2026-06-07 17:29:55 -07:00
feishu_comment.py chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) 2026-05-11 11:13:25 -07:00
feishu_comment_rules.py chore: ruff auto-fix C401, C416, C408, PLR1722 (#23940) 2026-05-11 11:20:58 -07:00
feishu_meeting_invite.py refactor(feishu): slim meeting-invite parser; add AUTHOR_MAP entry 2026-06-04 06:15:23 -07:00
helpers.py fix(gateway): preserve underscores in plain-text identifiers 2026-05-16 23:11:43 -07:00
matrix.py fix(matrix): preserve markdown table structure 2026-06-13 06:57:08 -07:00
msgraph_webhook.py fix(security): require source CIDR allowlisting for public msgraph webhook binds 2026-05-28 01:26:18 -07:00
signal.py fix(gateway): classify Signal video attachments + catch-all DOCUMENT fallback 2026-06-12 01:07:50 -07:00
signal_rate_limit.py feat(gateway/signal): add support for multiple images sending 2026-04-30 04:28:08 -07:00
slack.py fix(slack): make video attachments available to agents (#45512) 2026-06-13 03:33:27 -07:00
sms.py fix(gateway): add trust_env=True to aiohttp sessions in SMS, Slack, Teams, Google Chat adapters 2026-05-16 23:11:43 -07:00
telegram.py fix(telegram): resolve replies to rich (sendRichMessage) messages 2026-06-16 13:04:20 -07:00
telegram_network.py fix(telegram): reset sticky fallback IP on connect failure, retry primary DNS 2026-05-18 22:14:45 -07:00
webhook.py perf(webhook): prune request caches incrementally (#46065) 2026-06-14 02:40:54 -07:00
wecom.py fix(gateway): honor WECOM_ALLOWED_USERS in env-only WeCom DM allowlist 2026-06-01 19:20:36 -07:00
wecom_callback.py chore(wecom): make defusedxml dep acquireable and tolerant of absence 2026-05-25 23:30:43 -07:00
wecom_crypto.py feat(gateway): add WeCom callback-mode adapter for self-built apps 2026-04-11 15:22:49 -07:00
weixin.py fix(weixin): add rate-limit circuit breaker 2026-06-07 22:10:17 -07:00
whatsapp.py fix(gateway): accept metadata kwarg in WhatsApp/email send_image 2026-06-16 06:23:53 -07:00
whatsapp_cloud.py fix(whatsapp-cloud): review follow-ups for #43921 2026-06-11 07:51:01 -07:00
whatsapp_common.py fix(whatsapp-cloud): review follow-ups for #43921 2026-06-11 07:51:01 -07:00
yuanbao.py feat(Yuanbao): support wechat forward msg (#43508) 2026-06-12 02:06:47 -07:00
yuanbao_media.py chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937) 2026-05-11 11:13:25 -07:00
yuanbao_proto.py feat(Yuanbao): support wechat forward msg (#43508) 2026-06-12 02:06:47 -07:00
yuanbao_sticker.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00