From ecf2118bb25d6fa7806cd2bad2ff349d650fe381 Mon Sep 17 00:00:00 2001 From: MervinPraison <454862+MervinPraison@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:06:59 +0000 Subject: [PATCH] fix(voice-agent): remove committed run logs, timing-safe webhook verify, lazy httpx - Delete accidentally committed .app.err / .tunnel.log.err (leaked local paths, Cloudflare tunnel URLs, connector IDs); ignore *.err - verify.py: use hmac.compare_digest for constant-time secret comparison - client.py: lazy-import httpx (optional dep) per AGENTS.md lazy-import rule - Fix import ordering (ruff I001) in app.py / voice_routes.py Co-Authored-By: Claude Opus 4.8 --- .gitignore | 1 + examples/python/voice-agent/.app.err | 34 ------------ examples/python/voice-agent/.tunnel.log.err | 52 ------------------- examples/python/voice-agent/app.py | 9 ++-- .../voice-agent/integrations/voice/client.py | 4 +- .../voice-agent/integrations/voice/verify.py | 4 +- examples/python/voice-agent/voice_routes.py | 5 +- 7 files changed, 12 insertions(+), 97 deletions(-) delete mode 100644 examples/python/voice-agent/.app.err delete mode 100644 examples/python/voice-agent/.tunnel.log.err diff --git a/.gitignore b/.gitignore index 5b29e27..0d38c34 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ Thumbs.db # Project specific aiui/ *.log +*.err .praison/ .gemini/ .internal/ diff --git a/examples/python/voice-agent/.app.err b/examples/python/voice-agent/.app.err deleted file mode 100644 index 688190c..0000000 --- a/examples/python/voice-agent/.app.err +++ /dev/null @@ -1,34 +0,0 @@ -[19:22:22] server.py:902 WARNING No auth_token provided for Gateway server. Generated temporary token: gw_****bfdf. For production, set GATEWAY_AUTH_TOKEN. -INFO: Started server process [16168] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit) -C:\Users\DELL\AppData\Local\Programs\Python\Python313\Lib\site-packages\praisonaiagents\_warning_patch.py:50: RuntimeWarning: Failed to read version from pyproject.toml: Version not found in pyproject.toml. Using 'unknown'. - _original_warn(message, category, stacklevel, source) -[19:22:26] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-8e0d97fd8839). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:27] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-b8bcf4e24c8f). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:28] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-5f0a892fe1a0). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:29] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-e985038c68b7). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:29] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-5b10026bd4ce). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:29] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-c709a6041f47). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:30] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-4079e4963da6). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:31] agent.py:5802 WARNING Agent memory enabled without an explicit user_id; using an auto-generated, non-shared id (agent-85acb1a5e90c). Pass user_id=... explicitly to persist/share memory across runs. -[19:22:33] server.py:48 WARNING Jobs API using in-memory store - jobs and idempotency keys are LOST on restart. Set PRAISONAI_JOBS_DB_PATH to persist across restarts. -INFO: 127.0.0.1:51238 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed -INFO: 127.0.0.1:51312 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed -INFO: 127.0.0.1:51493 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed -INFO: 127.0.0.1:51501 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed -INFO: 127.0.0.1:51848 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed -INFO: 127.0.0.1:51883 - "WebSocket /api/chat/ws" [accepted] -INFO: connection open -INFO: connection closed diff --git a/examples/python/voice-agent/.tunnel.log.err b/examples/python/voice-agent/.tunnel.log.err deleted file mode 100644 index 107e74e..0000000 --- a/examples/python/voice-agent/.tunnel.log.err +++ /dev/null @@ -1,52 +0,0 @@ -2026-09-04T13:49:40Z INF Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee, are subject to the Cloudflare Online Services Terms of Use (https://www.cloudflare.com/website-terms/), and Cloudflare reserves the right to investigate your use of Tunnels for violations of such terms. If you intend to use Tunnels in production you should use a pre-created named tunnel by following: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps -2026-09-04T13:49:40Z INF Requesting new quick Tunnel on trycloudflare.com... -2026-09-04T13:49:47Z INF +--------------------------------------------------------------------------------------------+ -2026-09-04T13:49:47Z INF | Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): | -2026-09-04T13:49:47Z INF | https://sleeve-polyphonic-wagon-census.trycloudflare.com | -2026-09-04T13:49:47Z INF +--------------------------------------------------------------------------------------------+ -2026-09-04T13:49:47Z INF Cannot determine default configuration path. No file [config.yml config.yaml] in [~/.cloudflared ~/.cloudflare-warp ~/cloudflare-warp] -2026-09-04T13:49:47Z INF Version 2026.8.3 (Checksum 83e726ed18ea78c5ad5213c4c3a3a27051393950d2bc8ed4de69bec12d14eaae) -2026-09-04T13:49:47Z INF GOOS: windows, GOVersion: go1.26.4, GoArch: amd64 -2026-09-04T13:49:47Z INF Settings: map[ha-connections:1 protocol:quic url:http://127.0.0.1:8001] -2026-09-04T13:49:47Z INF cloudflared will not automatically update on Windows systems. -2026-09-04T13:49:47Z INF Generated Connector ID: be646d59-ad64-4576-8186-a66f11a2d56b -2026-09-04T13:49:47Z INF Initial protocol quic -2026-09-04T13:49:47Z INF ICMP proxy will use 10.160.21.143 as source for IPv4 -2026-09-04T13:49:47Z INF ICMP proxy will use fe80::17a3:312a:8442:a6fa in zone Wi-Fi as source for IPv6 -2026-09-04T13:49:47Z INF cloudflared does not support loading the system root certificate pool on Windows. Please use --origin-ca-pool to specify the path to the certificate pool -2026-09-04T13:49:47Z INF ICMP proxy will use 10.160.21.143 as source for IPv4 -2026-09-04T13:49:47Z INF Tunnel connection curve preferences: [X25519MLKEM768 CurveID(65074) CurveP256] connIndex=0 event=0 ip=198.41.192.7 -2026-09-04T13:49:47Z INF ICMP proxy will use fe80::17a3:312a:8442:a6fa in zone Wi-Fi as source for IPv6 -2026-09-04T13:49:47Z INF Starting metrics server on 127.0.0.1:20241/metrics -2026-09-04T13:49:47Z INF +-------------------------------------------------------------------------------------+ -2026-09-04T13:49:47Z INF | CONNECTIVITY PRE-CHECKS | -2026-09-04T13:49:47Z INF +-------------------------------------------------------------------------------------+ -2026-09-04T13:49:47Z INF | COMPONENT TARGET STATUS DETAILS | -2026-09-04T13:49:47Z INF | DNS Resolution region1.v2.argotunnel.com PASS DNS Resolved successfully | -2026-09-04T13:49:47Z INF | DNS Resolution region2.v2.argotunnel.com PASS DNS Resolved successfully | -2026-09-04T13:49:47Z INF | UDP Connectivity region1.v2.argotunnel.com PASS QUIC connection successful | -2026-09-04T13:49:47Z INF | UDP Connectivity region2.v2.argotunnel.com PASS QUIC connection successful | -2026-09-04T13:49:47Z INF | TCP Connectivity region1.v2.argotunnel.com PASS HTTP/2 connection successful | -2026-09-04T13:49:47Z INF | TCP Connectivity region2.v2.argotunnel.com PASS HTTP/2 connection successful | -2026-09-04T13:49:47Z INF | Cloudflare API api.cloudflare.com:443 PASS API is reachable | -2026-09-04T13:49:47Z INF | | -2026-09-04T13:49:47Z INF | SUMMARY: Environment is healthy. cloudflared will use 'quic' as primary protocol. | -2026-09-04T13:49:47Z INF +-------------------------------------------------------------------------------------+ -2026-09-04T13:49:47Z INF precheck component="DNS Resolution" details="DNS Resolved successfully" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region1.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="DNS Resolution" details="DNS Resolved successfully" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region2.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="UDP Connectivity" details="QUIC connection successful" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region1.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="UDP Connectivity" details="QUIC connection successful" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region2.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="TCP Connectivity" details="HTTP/2 connection successful" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region1.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="TCP Connectivity" details="HTTP/2 connection successful" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=region2.v2.argotunnel.com -2026-09-04T13:49:47Z INF precheck component="Cloudflare API" details="API is reachable" run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 status=pass target=api.cloudflare.com:443 -2026-09-04T13:49:47Z INF precheck complete hard_fail=false run_id=a43185e9-c9c6-4291-87b1-6e9aa877a683 suggested_protocol=quic -2026-09-04T13:49:47Z INF Registered tunnel connection connIndex=0 connection=6ddd478d-e87d-483e-b10d-1c842a230609 event=0 ip=198.41.192.7 location=maa01 protocol=quic -2026-09-04T14:06:25Z INF Initiating graceful shutdown due to signal terminated ... -2026-09-04T14:06:25Z ERR failed to run the datagram handler error="context canceled" connIndex=0 event=0 ip=198.41.192.7 -2026-09-04T14:06:25Z ERR failed to serve tunnel connection error="accept stream listener encountered a failure while serving" connIndex=0 event=0 ip=198.41.192.7 -2026-09-04T14:06:25Z ERR Serve tunnel error error="accept stream listener encountered a failure while serving" connIndex=0 event=0 ip=198.41.192.7 -2026-09-04T14:06:25Z INF Retrying connection in up to 1s connIndex=0 event=0 ip=198.41.192.7 -2026-09-04T14:06:25Z ERR Connection terminated connIndex=0 -2026-09-04T14:06:25Z ERR no more connections active and exiting -2026-09-04T14:06:25Z INF Tunnel server stopped -2026-09-04T14:06:25Z INF Metrics server stopped diff --git a/examples/python/voice-agent/app.py b/examples/python/voice-agent/app.py index 6c88f66..eea9ca6 100644 --- a/examples/python/voice-agent/app.py +++ b/examples/python/voice-agent/app.py @@ -20,10 +20,7 @@ import sys from pathlib import Path from typing import Any -import praisonaiui as aiui -from praisonaiui.server import create_app from starlette.routing import Route - from voice_routes import ( api_voice_call_detail, api_voice_config_status, @@ -32,6 +29,9 @@ from voice_routes import ( webhook_voice, ) +import praisonaiui as aiui +from praisonaiui.server import create_app + _EXAMPLE_DIR = Path(__file__).resolve().parent if str(_EXAMPLE_DIR) not in sys.path: sys.path.insert(0, str(_EXAMPLE_DIR)) @@ -63,9 +63,8 @@ def get_agent(): global _agent if _agent is not None: return _agent - from praisonaiagents import Agent - from integrations.voice.tools import echo_message, get_current_time, register_tool + from praisonaiagents import Agent register_tool("get_current_time", get_current_time) register_tool("echo_message", echo_message) diff --git a/examples/python/voice-agent/integrations/voice/client.py b/examples/python/voice-agent/integrations/voice/client.py index 97f07a0..0804f7e 100644 --- a/examples/python/voice-agent/integrations/voice/client.py +++ b/examples/python/voice-agent/integrations/voice/client.py @@ -5,8 +5,6 @@ from __future__ import annotations import logging from typing import Any -import httpx - from integrations.voice.config import VoiceSettings logger = logging.getLogger(__name__) @@ -39,6 +37,8 @@ class VoiceClient: *, json_body: dict[str, Any] | None = None, ) -> dict[str, Any]: + import httpx # lazy: optional dependency for the example (pip install httpx) + url = f"{self._settings.api_base.rstrip('/')}{path}" with httpx.Client(timeout=30.0) as client: response = client.request(method, url, headers=self._headers(), json=json_body) diff --git a/examples/python/voice-agent/integrations/voice/verify.py b/examples/python/voice-agent/integrations/voice/verify.py index d214dfa..c13b28c 100644 --- a/examples/python/voice-agent/integrations/voice/verify.py +++ b/examples/python/voice-agent/integrations/voice/verify.py @@ -2,6 +2,8 @@ from __future__ import annotations +import hmac + _SECRET_HEADERS = ("x-voice-webhook-secret", "x-webhook-secret") @@ -15,5 +17,5 @@ def verify_webhook_request(*, secret: str | None, headers: dict[str, str]) -> No return lowered = {k.lower(): v for k, v in headers.items()} incoming = next((lowered[name] for name in _SECRET_HEADERS if lowered.get(name)), None) - if not incoming or incoming != secret: + if not incoming or not hmac.compare_digest(incoming, secret): raise VerificationError("Invalid or missing webhook secret header") diff --git a/examples/python/voice-agent/voice_routes.py b/examples/python/voice-agent/voice_routes.py index 32809fd..5830b97 100644 --- a/examples/python/voice-agent/voice_routes.py +++ b/examples/python/voice-agent/voice_routes.py @@ -6,14 +6,13 @@ import asyncio import json import logging -from starlette.requests import Request -from starlette.responses import JSONResponse, PlainTextResponse, Response - from integrations.voice.client import VoiceClient from integrations.voice.config import VoiceConfigError, load_voice_settings from integrations.voice.processor import process_voice_webhook from integrations.voice.store import VoiceCallStore from integrations.voice.verify import VerificationError, verify_webhook_request +from starlette.requests import Request +from starlette.responses import JSONResponse, PlainTextResponse, Response logger = logging.getLogger(__name__)