mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-14 04:02:26 +00:00
fix(msgraph_webhook): harden auth surface + IP allowlisting + response hygiene
Defense-in-depth polish on top of the webhook listener before it becomes a real attack surface once the pipeline starts creating subscriptions and Graph starts POSTing to the configured public URL. - Timing-safe clientState comparison. Previously used `==` on strings; switches to hmac.compare_digest so a mismatch does not leak how many leading characters matched. client_state is documented as a strong shared secret (openssl rand -hex 32 in the setup docs), so a timing-safe primitive is the right call. - Split GET and POST handlers. Graph validates a subscription by sending GET with validationToken in the query; anything else on GET is now a 400 so the endpoint cannot be probed or mistakenly used for data exfil. Previously a bare GET fell through to the POST path and blew up on request.json() with a confusing 400. - Empty response bodies on success. 202 is returned with no body so internal counters (accepted / duplicates / scheduled) do not leak to any caller that can reach the endpoint; counters remain observable via /health for operators. 403 on every-item-bad-clientState batches (so forged POSTs stop retrying), 400 on malformed / unknown-resource batches (sender configuration issue). - Optional source-IP allowlist. New `allowed_source_cidrs` extra field (list or comma-separated string) and `MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS` env var let operators restrict the webhook to Microsoft Graph's published webhook source ranges in production. Empty = allow all, preserving dev-tunnel / localhost workflows. Invalid CIDRs are logged and ignored rather than crashing. Also gates the handshake endpoint so disallowed IPs cannot probe it. - Tests updated for the new response contract (empty-body 202, auth-only 403, config-error 400) and extended to cover: bare GET rejection, POST-with-validationToken handshake tolerance, timing-safe compare actually invoked via hmac.compare_digest spy, malformed body / missing value array, IP allowlist accept/reject paths, handshake IP allowlist, invalid CIDR entries, comma-string CIDR list parsing. 52/52 passed (was 40). Full gateway suite: 5049 passed / 1 pre-existing failure in test_discord_free_response (unrelated, reproduces on clean origin/main).
This commit is contained in:
parent
26a59e4f6c
commit
b8d7e0e6d3
3 changed files with 301 additions and 49 deletions
|
|
@ -1418,12 +1418,16 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
msgraph_webhook_port = os.getenv("MSGRAPH_WEBHOOK_PORT")
|
||||
msgraph_webhook_client_state = os.getenv("MSGRAPH_WEBHOOK_CLIENT_STATE", "")
|
||||
msgraph_webhook_resources = os.getenv("MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES", "")
|
||||
msgraph_webhook_allowed_cidrs = os.getenv(
|
||||
"MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS", ""
|
||||
)
|
||||
if (
|
||||
msgraph_webhook_enabled
|
||||
or Platform.MSGRAPH_WEBHOOK in config.platforms
|
||||
or msgraph_webhook_port
|
||||
or msgraph_webhook_client_state
|
||||
or msgraph_webhook_resources
|
||||
or msgraph_webhook_allowed_cidrs
|
||||
):
|
||||
if Platform.MSGRAPH_WEBHOOK not in config.platforms:
|
||||
config.platforms[Platform.MSGRAPH_WEBHOOK] = PlatformConfig()
|
||||
|
|
@ -1450,6 +1454,16 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
config.platforms[Platform.MSGRAPH_WEBHOOK].extra[
|
||||
"accepted_resources"
|
||||
] = resources
|
||||
if msgraph_webhook_allowed_cidrs:
|
||||
cidrs = [
|
||||
cidr.strip()
|
||||
for cidr in msgraph_webhook_allowed_cidrs.split(",")
|
||||
if cidr.strip()
|
||||
]
|
||||
if cidrs:
|
||||
config.platforms[Platform.MSGRAPH_WEBHOOK].extra[
|
||||
"allowed_source_cidrs"
|
||||
] = cidrs
|
||||
|
||||
# DingTalk
|
||||
dingtalk_client_id = os.getenv("DINGTALK_CLIENT_ID")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue