PyPI / websockets / 17.3

websockets 17.3

Source
https://github.com/python-websockets/websockets
Tag scanned
17.3
Compared with
17.2 (0 signals introduced since)
Files
299 scanned, 53 skipped (binary, oversized, or unrecognised)
Signals
1 critical · 0 warn · 43 info
Scan time
28.2 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 35 of 35 warn and critical signals; 34 demoted to info.
Disclude version
4.1.0
Scanned (UTC)
2026-10-21 23:29

Signals

Ordered by severity, then signals introduced in this release. Hidden characters are shown as ⟨U+…⟩.

  1. critical dynamic-execution experiments/compression/corpus.py:30:18

    `subprocess.check_output` with `shell=True` and a non-literal command — runtime-determined shell invocation (ast pass, confidence 0.85)

            sha = todo.pop(0)
            commit = subprocess.check_output(COMMIT_API.replace(":sha", sha), shell=True)
            commits.append(commit)
            seen.add(sha)
    likely benign LLM, confidence 0.60 Experiment script builds a shell command from commit SHAs and runs it with shell=True.
    Reasoning

    The command is built from a SHA obtained from the GitHub API and a template, then run with shell=True. That is a real injection-risk pattern, but the input comes from a repository's own commit history in a benchmark script, with no sign of deliberate malice or obfuscation.

  2. info warn → info by review whitespace-anomaly docs/deploy/heroku.rst:74:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        [main 8418c62] Initial implementation.
        ⟨U+00A0⟩3 files changed, 32 insertions(+)
        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
    likely benign LLM, confidence 0.90 Indented git commit output in a Heroku deployment tutorial contains a non-breaking space.
    Reasoning

    The flagged character is a non-breaking space in indentation of a pasted git command transcript in RST documentation. This is a rendering or copy-paste artifact in a benign tutorial, not an obfuscation technique.

  3. info warn → info by review whitespace-anomaly docs/deploy/heroku.rst:75:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩3 files changed, 32 insertions(+)
        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    likely benign LLM, confidence 0.90 Git output lines in the Heroku tutorial include a non-breaking space in indentation.
    Reasoning

    Same as the other heroku.rst findings: a non-breaking space in the indentation of documented git output. It has no executable effect and fits ordinary documentation formatting.

  4. info warn → info by review whitespace-anomaly docs/deploy/heroku.rst:76:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    likely benign LLM, confidence 0.90 Git output in the Heroku tutorial has a non-breaking space in indentation.
    Reasoning

    A raw-pass whitespace signal on documentation prose containing git output. The pattern is common in copied terminal output and is not a meaningful threat.

  5. info warn → info by review whitespace-anomaly docs/deploy/heroku.rst:77:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    
    The app is ready. Let's deploy it!
    likely benign LLM, confidence 0.90 Git output in the Heroku tutorial has a non-breaking space in indentation.
    Reasoning

    Same non-breaking-space indentation in a documentation transcript of git output. Raw whitespace anomalies in RST docs have a high false-positive rate and this one is benign.

  6. info warn → info by review whitespace-anomaly docs/deploy/heroku.rst:112:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        To https://git.heroku.com/websockets-echo.git
        ⟨U+00A0⟩* [new branch]      main -> main
    
    Validate deployment
    likely benign LLM, confidence 0.90 Git push output in the Heroku tutorial has a non-breaking space in indentation.
    Reasoning

    A non-breaking space in the indentation of a git push transcript in documentation. It is cosmetic and does not indicate hidden instructions or code.

  7. info warn → info by review whitespace-anomaly docs/deploy/koyeb.rst:77:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        [main f634b8b] Initial implementation.
        ⟨U+00A0⟩3 files changed, 39 insertions(+)
        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
    likely benign LLM, confidence 0.90 Git output in the Koyeb tutorial includes a non-breaking space in indentation.
    Reasoning

    Non-breaking space in indentation of documented git output in a Koyeb deployment tutorial. It is a benign formatting artifact.

  8. info warn → info by review whitespace-anomaly docs/deploy/koyeb.rst:78:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩3 files changed, 39 insertions(+)
        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    likely benign LLM, confidence 0.90 Git output in the Koyeb tutorial has a non-breaking space in indentation.
    Reasoning

    Same non-breaking-space indentation in a documented git transcript. This is a raw-pass false positive in documentation.

  9. info warn → info by review whitespace-anomaly docs/deploy/koyeb.rst:79:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩create mode 100644 Procfile
        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    likely benign LLM, confidence 0.90 Git output in the Koyeb tutorial has a non-breaking space in indentation.
    Reasoning

    Non-breaking space in indentation of a documented command transcript. It has no execution path and is benign.

  10. info warn → info by review whitespace-anomaly docs/deploy/koyeb.rst:80:5

    [embedded bash] invisible whitespace U+00A0 in indentation (raw pass, confidence 0.80)

        ⟨U+00A0⟩create mode 100644 app.py
        ⟨U+00A0⟩create mode 100644 requirements.txt
    
    The app is ready. Let's deploy it!
    likely benign LLM, confidence 0.90 Git output in the Koyeb tutorial has a non-breaking space in indentation.
    Reasoning

    Same benign non-breaking-space formatting artifact in a Koyeb tutorial transcript.

  11. info critical → info by review path-command-shadow docs/deploy/koyeb.rst:127:37

    [embedded bash] redirect writes to `id` — filename matches a system command (PATH hijack setup) (ast pass, confidence 0.88)

    
        $ websockets wss://<app>-<user>-<id>.koyeb.app/
        Connected to wss://<app>-<user>-<id>.koyeb.app/.
        >
    dismissed LLM, confidence 0.92 Documentation transcript of a websocket client session was misparsed as a shell redirect to a file named id.
    Reasoning

    The `id` in the flagged redirect is a URL placeholder (<id>) inside a websocket URL. The '>' is a client prompt or output marker in a transcript, not a shell redirect. The critical PATH-hijack label is a parser misreading of documentation text.

  12. info critical → info by review path-command-shadow docs/deploy/koyeb.rst:148:37

    [embedded bash] redirect writes to `id` — filename matches a system command (PATH hijack setup) (ast pass, confidence 0.88)

    
        $ websockets wss://<app>-<user>-<id>.koyeb.app/
        Connected to wss://<app>-<user>-<id>.koyeb.app/.
        >
    dismissed LLM, confidence 0.92 Websocket client transcript in the Koyeb docs was misread as a redirect into a file named id.
    Reasoning

    Same pattern: the '>' is a websocket REPL prompt and '<id>' is a placeholder in a URL. No actual file write or PATH manipulation occurs.

  13. info critical → info by review path-command-shadow docs/deploy/koyeb.rst:159:37

    [embedded bash] redirect writes to `id` — filename matches a system command (PATH hijack setup) (ast pass, confidence 0.88)

    
        $ websockets wss://<app>-<user>-<id>.koyeb.app/
        Connected to wss://<app>-<user>-<id>.koyeb.app/.
        Connection closed: 1001 (going away).
    dismissed LLM, confidence 0.92 Websocket session transcript in the Koyeb docs was misread as a redirect to a file named id.
    Reasoning

    The flagged text is a URL placeholder and a client prompt in a documented session. The shell-redirect interpretation is a false positive.

  14. info critical → info by review path-command-shadow docs/intro/tutorial3.rst:190:37

    [embedded bash] redirect writes to `id` — filename matches a system command (PATH hijack setup) (ast pass, confidence 0.88)

    
        $ websockets wss://<app>-<user>-<id>.koyeb.app/
        Connected to wss://<app>-<user>-<id>.koyeb.app/.
        > {"type": "init"}
    dismissed LLM, confidence 0.92 Tutorial transcript showing a websocket client session was misparsed as a redirect to a file named id.
    Reasoning

    The `<id>` placeholder sits in a websocket URL and the '>' is a REPL prompt in a tutorial transcript. Nothing is written to a file and no PATH is altered.

  15. info warn → info by review dynamic-execution experiments/compression/corpus.py:20:12

    `subprocess.check_output` called with `shell=True` and a literal command (ast pass, confidence 0.70)

    
        head = subprocess.check_output(
            "git rev-parse origin/main",
            shell=True,
    likely benign LLM, confidence 0.85 Experiment script runs a fixed git rev-parse command through the shell.
    Reasoning

    The command is a fixed literal string (git rev-parse origin/main), so shell=True carries no injection from external input. It is an ordinary, if unnecessary, use of subprocess in an experiment script.

  16. info critical → info by review dynamic-execution setup.py:10:1

    `exec` at module scope (runs on import) on a non-literal expression (ast pass, confidence 0.80)

    
    exec((root_dir / "src" / "websockets" / "version.py").read_text(encoding="utf-8"))
    
    # PyPI disables the "raw" directive. Remove this section of the README.
    likely benign LLM, confidence 0.90 setup.py execs the package's own version.py to read __version__.
    Reasoning

    The exec runs the package's own src/websockets/version.py read from the project tree during setup. It is a common version-loading idiom, not hidden payload execution, though exec on a file read is a pattern worth noting.

  17. info warn → info by review dynamic-execution src/websockets/cli.py:214:9

    `os.system` called with a literal shell command (ast pass, confidence 0.70)

        if sys.platform == "win32":
            os.system("")
    
        try:
    likely benign LLM, confidence 0.90 Empty os.system call on Windows to enable ANSI terminal colors.
    Reasoning

    Calling os.system with an empty string on win32 is a known trick to enable ANSI escape handling in the Windows console. It has no malicious effect.

  18. info warn → info by review decoder-decompress-payload src/websockets/extensions/permessage_deflate.py:72:28

    file imports decoder module(s) zlib and calls `.decompress()` — embedded payload decompressed at runtime (ast pass, confidence 0.75)

            if not self.remote_no_context_takeover:
                self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits)
    
            if not self.local_no_context_takeover:
    likely benign LLM, confidence 0.95 zlib decompressobj used for WebSocket permessage-deflate decoding.
    Reasoning

    This is a standard zlib decompressobj used to implement the permessage-deflate WebSocket extension (RFC 7692). It decompresses network frame data, not an embedded payload.

  19. info critical → info by review dynamic-import src/websockets/imports.py:28:14

    `__import__` called on a non-literal expression (ast pass, confidence 0.70)

            assert level < len(source), "importing from parent isn't supported"
        module = __import__(source[level:], namespace, None, [name], level)
        return getattr(module, name)
    likely benign LLM, confidence 0.85 __import__ used by a lazy-import helper to resolve relative module paths.
    Reasoning

    This is a lazy_import helper resolving a module name from a relative-import source string and level. The arguments come from library code, not untrusted input, and the module is then getattr'd for a named attribute.

  20. info warn → info by review dynamic-execution tests/asyncio/server.py:28:21

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            async for expr in ws:
                value = eval(expr)
                await ws.send(str(value))
        elif path == "/crash":
    likely benign LLM, confidence 0.80 Test server evals client messages to return computed values during tests.
    Reasoning

    This is a test fixture server that deliberately evals client-sent expressions to check latency and behavior. Evaluating network input is unsafe if deployed, but here it is a test-only harness.

  21. info warn → info by review dynamic-execution tests/asyncio/test_server.py:368:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertEqual(latency, 0)
                    await asyncio.sleep(2 * MS)
    likely benign LLM, confidence 0.90 Test evaluates the server's latency reply.
    Reasoning

    The test sends the literal string 'ws.latency' to a test server and evals the reply to check a numeric latency value. This is test scaffolding with no malicious intent.

  22. info warn → info by review dynamic-execution tests/asyncio/test_server.py:372:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertGreater(latency, 0)
    likely benign LLM, confidence 0.90 Test evaluates the server's latency reply.
    Reasoning

    Same test pattern as the preceding finding: evaluating a latency reply from the test server it controls.

  23. info warn → info by review dynamic-execution tests/asyncio/test_server.py:382:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertEqual(latency, 0)
    likely benign LLM, confidence 0.90 Test evaluates the server's latency reply.
    Reasoning

    Same test pattern: evaluating a latency reply from the test server it controls.

  24. info warn → info by review dynamic-execution tests/extensions/test_permessage_deflate.py:60:35

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

        def test_repr(self):
            self.assertExtensionEqual(eval(repr(self.extension)), self.extension)
    
        # Control frames aren't encoded or decoded.
    likely benign LLM, confidence 0.95 Round-trip test of extension repr via eval.
    Reasoning

    eval(repr(extension)) is a round-trip check that the extension's repr reconstructs an equal object. It operates on the project's own object.

  25. info warn → info by review dynamic-execution tests/legacy/test_client_server.py:841:26

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            close_timeout = self.loop.run_until_complete(self.client.recv())
            self.assertEqual(eval(close_timeout), 7)
    
        @with_server(timeout=6, deprecation_warnings=["rename timeout to close_timeout"])
    likely benign LLM, confidence 0.90 Test evals a numeric close_timeout reply from its test server.
    Reasoning

    The test evals a close_timeout value sent by its own test server to assert a numeric setting. This is test code with no malicious intent.

  26. info warn → info by review dynamic-execution tests/legacy/test_client_server.py:847:26

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            close_timeout = self.loop.run_until_complete(self.client.recv())
            self.assertEqual(eval(close_timeout), 6)
    
        @with_server(
    likely benign LLM, confidence 0.90 Test evals a numeric close_timeout reply from its test server.
    Reasoning

    Same close_timeout round-trip test pattern as the preceding finding.

  27. info warn → info by review dynamic-execution tests/legacy/test_client_server.py:857:26

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            close_timeout = self.loop.run_until_complete(self.client.recv())
            self.assertEqual(eval(close_timeout), 7)
    
        @with_server()
    likely benign LLM, confidence 0.90 Test evals a numeric close_timeout reply from its test server.
    Reasoning

    Same close_timeout round-trip test pattern as the preceding findings.

  28. info warn → info by review dynamic-execution tests/sync/server.py:29:21

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            for expr in ws:
                value = eval(expr)
                ws.send(str(value))
        elif path == "/crash":
    likely benign LLM, confidence 0.80 Sync test server evals client messages to return computed values.
    Reasoning

    Sync counterpart of the asyncio test server that evals client expressions for latency testing. Test-only harness, unsafe pattern but no malicious intent.

  29. info warn → info by review dynamic-execution tests/sync/test_server.py:263:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    client.send("ws.latency")
                    latency = eval(client.recv())
                    self.assertEqual(latency, 0)
                    time.sleep(2 * MS)
    likely benign LLM, confidence 0.90 Sync test evaluates the server's latency reply.
    Reasoning

    Test sends 'ws.latency' and evals the reply to check latency. Test scaffolding only.

  30. info warn → info by review dynamic-execution tests/sync/test_server.py:267:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    client.send("ws.latency")
                    latency = eval(client.recv())
                    self.assertGreater(latency, 0)
    likely benign LLM, confidence 0.90 Sync test evaluates the server's latency reply.
    Reasoning

    Same sync latency test pattern as the preceding finding.

  31. info warn → info by review dynamic-execution tests/sync/test_server.py:276:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    client.send("ws.latency")
                    latency = eval(client.recv())
                    self.assertEqual(latency, 0)
    likely benign LLM, confidence 0.85 Test code evaluates a latency expression returned over a websocket by the test's own server.
    Reasoning

    The eval runs on a reply received from the test's own client, in a test that asks the server for a 'ws.latency' value. It is a test-harness pattern on localhost, not an obfuscated payload. The residual concern is that eval of network input is unsafe if the server were exposed.

  32. info unicode-homoglyph tests/test_frames.py:496:14

    identifier `α` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

                "a",
                "α",
                "あ",
                "😀",
  33. info unicode-homoglyph tests/test_frames.py:499:14

    identifier `aαあ` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

                "😀",
                "aαあ😀",
                "😀あαa",
            ]
  34. info unicode-mixed-script tests/test_frames.py:499:14

    identifier `aαあ` mixes scripts: Latin + Greek + Hiragana (in string/comment) (raw pass, confidence 0.40)

                "😀",
                "aαあ😀",
                "😀あαa",
            ]
  35. info unicode-homoglyph tests/test_frames.py:500:18

    identifier `あαa` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

                "aαあ😀",
                "😀あαa",
            ]
        ]
  36. info unicode-mixed-script tests/test_frames.py:500:18

    identifier `あαa` mixes scripts: Hiragana + Greek + Latin (in string/comment) (raw pass, confidence 0.40)

                "aαあ😀",
                "😀あαa",
            ]
        ]
  37. info unicode-mixed-script tests/test_frames.py:526:17

    identifier `αあ` mixes scripts: Greek + Hiragana (in string/comment) (raw pass, confidence 0.40)

        def test_is_utf8_fragment_must_start_clean(self):
            data = "αあ".encode()
            self.assertTrue(is_utf8_fragment(data[:-1], must_start_clean=True))
            self.assertFalse(is_utf8_fragment(data[1:-1], must_start_clean=True))
  38. info unicode-mixed-script tests/test_frames.py:531:17

    identifier `αあ` mixes scripts: Greek + Hiragana (in string/comment) (raw pass, confidence 0.40)

        def test_is_utf8_fragment_must_end_clean(self):
            data = "αあ".encode()
            self.assertTrue(is_utf8_fragment(data[1:], must_end_clean=True))
            self.assertFalse(is_utf8_fragment(data[1:-1], must_end_clean=True))
  39. info unicode-mixed-script tests/test_uri.py:41:33

    identifier `πass` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

        (
            "ws://üser:påss@høst/πass?qùéry",
            WebSocketURI(
                False,
  40. info unicode-mixed-script tests/test_uri.py:66:18

    identifier `πass` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

        ("ws://localhost/path?query", "/path?query"),
        ("ws://høst/πass?qùéry", "/%CF%80ass?q%C3%B9%C3%A9ry"),
    ]
  41. info warn → info by review dynamic-execution tests/trio/server.py:34:21

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            async for expr in ws:
                value = eval(expr)
                await ws.send(str(value))
        elif path == "/crash":
    likely benign LLM, confidence 0.80 Test fixture server evaluates expressions sent over a websocket.
    Reasoning

    This is a test fixture server that evals each message received on its websocket, alongside a '/crash' path, which suggests a deliberately simple test endpoint. The eval is of network input, so it would be dangerous if deployed, but nothing in the snippet points to a dropper or hidden logic.

  42. info warn → info by review dynamic-execution tests/trio/test_server.py:365:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertEqual(latency, 0)
                    await trio.sleep(2 * MS)
    likely benign LLM, confidence 0.85 Async test evaluates a latency value returned by its own test server.
    Reasoning

    The eval evaluates the 'ws.latency' response from the test's own server, and the assertions show it checks a numeric latency. This is ordinary test code.

  43. info warn → info by review dynamic-execution tests/trio/test_server.py:369:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertGreater(latency, 0)
    likely benign LLM, confidence 0.85 Async test evaluates a latency reply and asserts it is positive.
    Reasoning

    Same pattern as the other test: eval of the 'ws.latency' reply from the local test server, followed by a greater-than-zero assertion. No malicious indicators.

  44. info warn → info by review dynamic-execution tests/trio/test_server.py:378:27

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    await client.send("ws.latency")
                    latency = eval(await client.recv())
                    self.assertEqual(latency, 0)
    likely benign LLM, confidence 0.85 Async test evaluates a latency reply from its own test server.
    Reasoning

    Repeats the same eval of a 'ws.latency' reply from the local test server, with a zero assertion. Legitimate test code.

Reproduce This Scan

Run disclude locally to verify. Add --format sarif for SARIF output. Add --llm (with an API key in the environment) to include the LLM review.

cargo install disclude --version 4.1.0
git clone https://github.com/python-websockets/websockets websockets && cd websockets
git checkout 17.3
disclude scan --severity info --diff 17.2 .