deepctl-cmd-debug-probe 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (14) hide show
  1. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/PKG-INFO +3 -3
  2. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/README.md +1 -1
  3. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/pyproject.toml +2 -2
  4. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/proxy.py +77 -2
  5. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/PKG-INFO +3 -3
  6. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/requires.txt +1 -1
  7. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/setup.cfg +0 -0
  8. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/__init__.py +0 -0
  9. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/command.py +0 -0
  10. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/models.py +0 -0
  11. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/SOURCES.txt +0 -0
  12. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/dependency_links.txt +0 -0
  13. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/entry_points.txt +0 -0
  14. {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-debug-probe
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Debug probe subcommand for deepctl — live ffprobe analysis during streaming
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -19,7 +19,7 @@ Requires-Dist: deepctl-shared-utils>=0.1.10
19
19
  Requires-Dist: click>=8.0.0
20
20
  Requires-Dist: rich>=13.0.0
21
21
  Requires-Dist: pydantic>=2.0.0
22
- Requires-Dist: aiohttp>=3.8.0
22
+ Requires-Dist: aiohttp>=3.11.0
23
23
 
24
24
  # deepctl-cmd-debug-probe
25
25
 
@@ -61,7 +61,7 @@ pipx run deepctl --help
61
61
  - `click>=8.0.0`
62
62
  - `rich>=13.0.0`
63
63
  - `pydantic>=2.0.0`
64
- - `aiohttp>=3.8.0`
64
+ - `aiohttp>=3.11.0`
65
65
 
66
66
  ## License
67
67
 
@@ -38,7 +38,7 @@ pipx run deepctl --help
38
38
  - `click>=8.0.0`
39
39
  - `rich>=13.0.0`
40
40
  - `pydantic>=2.0.0`
41
- - `aiohttp>=3.8.0`
41
+ - `aiohttp>=3.11.0`
42
42
 
43
43
  ## License
44
44
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-cmd-debug-probe"
7
- version = "0.0.2" # x-release-please-version
7
+ version = "0.0.3" # x-release-please-version
8
8
  description = "Debug probe subcommand for deepctl — live ffprobe analysis during streaming"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -26,7 +26,7 @@ dependencies = [
26
26
  "click>=8.0.0",
27
27
  "rich>=13.0.0",
28
28
  "pydantic>=2.0.0",
29
- "aiohttp>=3.8.0",
29
+ "aiohttp>=3.11.0",
30
30
  ]
31
31
 
32
32
  [project.scripts]
@@ -4,14 +4,17 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
+ import re
7
8
  import time
8
9
  import uuid
9
10
  from typing import TYPE_CHECKING
10
11
 
11
12
  import aiohttp
12
13
  from aiohttp import web
14
+ from deepctl_core.websocket import RedirectRefused, redirect_refused
13
15
  from deepctl_shared_utils import probe_buffer
14
16
  from rich.console import Console
17
+ from rich.markup import escape
15
18
 
16
19
  from .models import ProbeConnectionStats, ProbeSnapshot
17
20
 
@@ -20,6 +23,37 @@ if TYPE_CHECKING:
20
23
 
21
24
  console = Console()
22
25
 
26
+ # Browsers send Origin on every WebSocket handshake; native clients usually
27
+ # send none. Only pages served from this machine may use the proxy.
28
+ _LOOPBACK_ORIGIN = re.compile(
29
+ r"https?://(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]{1,5})?"
30
+ )
31
+
32
+
33
+ def _refuse_redirects(upstream_url: str) -> aiohttp.TraceConfig:
34
+ """A trace hook that stops aiohttp before it follows a redirect.
35
+
36
+ aiohttp's ws_connect has no allow_redirects switch; it follows a 3xx
37
+ on the upgrade and would then stream the audio to the new host. The
38
+ hook runs after the 3xx arrives and before any request to the new
39
+ location, so nothing reaches it.
40
+ """
41
+
42
+ async def on_redirect(
43
+ _session: aiohttp.ClientSession,
44
+ _ctx: object,
45
+ params: aiohttp.TraceRequestRedirectParams,
46
+ ) -> None:
47
+ location = params.response.headers.get("Location")
48
+ params.response.close()
49
+ raise redirect_refused(
50
+ upstream_url, location, command="dg debug probe", payload="audio"
51
+ )
52
+
53
+ trace = aiohttp.TraceConfig()
54
+ trace.on_request_redirect.append(on_redirect)
55
+ return trace
56
+
23
57
 
24
58
  class ProbeProxy:
25
59
  """WebSocket proxy that probes audio at configurable intervals."""
@@ -55,8 +89,42 @@ class ProbeProxy:
55
89
  return "agent"
56
90
  return "unknown"
57
91
 
58
- async def handle_connection(self, request: web.Request) -> web.WebSocketResponse:
92
+ def _refusal_reason(self, request: web.Request) -> str | None:
93
+ """Return why a connection is refused, or None to accept it.
94
+
95
+ Host must name this listener as 127.0.0.1:PORT or localhost:PORT.
96
+ Origin may be absent (native clients) or a loopback page origin;
97
+ anything else, including "null", is refused.
98
+ """
99
+ sockname = (
100
+ request.transport.get_extra_info("sockname")
101
+ if request.transport is not None
102
+ else None
103
+ )
104
+ if not sockname:
105
+ return "host"
106
+ port = sockname[1]
107
+ allowed_hosts = {f"127.0.0.1:{port}", f"localhost:{port}"}
108
+ if port == 80:
109
+ allowed_hosts |= {"127.0.0.1", "localhost"}
110
+ hosts = request.headers.getall("Host", [])
111
+ if len(hosts) != 1 or hosts[0].lower() not in allowed_hosts:
112
+ return "host"
113
+ origins = request.headers.getall("Origin", [])
114
+ if not origins:
115
+ return None
116
+ if len(origins) != 1 or not _LOOPBACK_ORIGIN.fullmatch(origins[0].lower()):
117
+ return "origin"
118
+ return None
119
+
120
+ async def handle_connection(self, request: web.Request) -> web.StreamResponse:
59
121
  """Handle an incoming WebSocket connection."""
122
+ # Checked before the handshake and before anything is sent upstream.
123
+ reason = self._refusal_reason(request)
124
+ if reason is not None:
125
+ console.print(escape(f"[conn] refused: {reason} not allowed"))
126
+ return web.Response(status=403, text="Forbidden\n")
127
+
60
128
  ws_client = web.WebSocketResponse()
61
129
  await ws_client.prepare(request)
62
130
 
@@ -91,7 +159,9 @@ class ProbeProxy:
91
159
 
92
160
  try:
93
161
  async with (
94
- aiohttp.ClientSession() as session,
162
+ aiohttp.ClientSession(
163
+ trace_configs=[_refuse_redirects(upstream_url)]
164
+ ) as session,
95
165
  session.ws_connect(
96
166
  upstream_url,
97
167
  headers=upstream_headers,
@@ -179,6 +249,11 @@ class ProbeProxy:
179
249
  # Suppress CancelledError from the cancelled task
180
250
  await asyncio.gather(task_c2u, task_u2c, return_exceptions=True)
181
251
 
252
+ except RedirectRefused as e:
253
+ console.print(
254
+ f"[red]Upstream connection refused[/red] [{conn_id}]: "
255
+ f"{escape(e.message)}"
256
+ )
182
257
  except aiohttp.ClientError as e:
183
258
  console.print(f"[red]Upstream connection failed[/red] [{conn_id}]: {e}")
184
259
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-debug-probe
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Debug probe subcommand for deepctl — live ffprobe analysis during streaming
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -19,7 +19,7 @@ Requires-Dist: deepctl-shared-utils>=0.1.10
19
19
  Requires-Dist: click>=8.0.0
20
20
  Requires-Dist: rich>=13.0.0
21
21
  Requires-Dist: pydantic>=2.0.0
22
- Requires-Dist: aiohttp>=3.8.0
22
+ Requires-Dist: aiohttp>=3.11.0
23
23
 
24
24
  # deepctl-cmd-debug-probe
25
25
 
@@ -61,7 +61,7 @@ pipx run deepctl --help
61
61
  - `click>=8.0.0`
62
62
  - `rich>=13.0.0`
63
63
  - `pydantic>=2.0.0`
64
- - `aiohttp>=3.8.0`
64
+ - `aiohttp>=3.11.0`
65
65
 
66
66
  ## License
67
67
 
@@ -3,4 +3,4 @@ deepctl-shared-utils>=0.1.10
3
3
  click>=8.0.0
4
4
  rich>=13.0.0
5
5
  pydantic>=2.0.0
6
- aiohttp>=3.8.0
6
+ aiohttp>=3.11.0