PyPI / websockets / 17.2

websockets 17.2

Source
https://github.com/python-websockets/websockets
Tag scanned
17.2
Compared with
17.1 (0 signals introduced since)
Files
284 scanned, 67 skipped (binary, oversized, or unrecognised)
Signals
1 critical · 0 warn · 43 info
Scan time
25.8 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 35 of 35 warn and critical signals; 34 demoted to info.
Disclude version
4.0.0
Scanned (UTC)
2026-10-14 18:19

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 Builds a shell command from a template with a sha value and runs it with shell=True.
    Reasoning

    The shell command is a constant template with a commit sha substituted in. The sha comes from git/API data, so this is a shell-injection risk in principle, but the snippet shows no attacker-controlled input path and the file is a compression experiment.

  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.85 Git output sample in Heroku deployment docs with a non-breaking space in its indentation.
    Reasoning

    Non-breaking space in the indentation of a terminal transcript (git commit output) is a typical artifact of copying text from a web page or terminal. Nothing in the snippet executes or hides code, and the rst file is documentation.

  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.85 Git output sample in Heroku deployment docs with a non-breaking space in its indentation.
    Reasoning

    Same pattern as the neighbouring finding: plain git output in a documentation code block, with a copy-paste whitespace artifact. Raw-pass findings like this have a high false-positive rate.

  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.85 Git output sample in Heroku deployment docs with a non-breaking space in its indentation.
    Reasoning

    Plain git output in a documentation code block. The U+00A0 is a whitespace artifact with no functional effect.

  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.85 Git output sample in Heroku deployment docs with a non-breaking space in its indentation.
    Reasoning

    Plain git output in a documentation code block. The non-breaking space is a copy-paste artifact in prose-like text.

  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.85 Git push output sample in Heroku docs with a non-breaking space in its indentation.
    Reasoning

    Git push output in a documentation code block with a non-breaking space in the indentation. This is a copy-paste artifact and has no executable effect.

  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.85 Git output sample in Koyeb deployment docs with a non-breaking space in its indentation.
    Reasoning

    Git output sample in a Koyeb deployment doc with a non-breaking space in its indentation. This is a documentation copy-paste 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.85 Git output sample in Koyeb deployment docs with a non-breaking space in its indentation.
    Reasoning

    Plain git output in a documentation code block. The non-breaking space is a whitespace artifact with no executable content.

  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.85 Git output sample in Koyeb deployment docs with a non-breaking space in its indentation.
    Reasoning

    Plain git output in a documentation code block with a copy-paste whitespace artifact.

  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.85 Git output sample in Koyeb deployment docs with a non-breaking space in its indentation.
    Reasoning

    Plain git output in a documentation code block with a copy-paste whitespace artifact.

  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.95 Documentation placeholder `<id>` in a websockets URL was misparsed as a shell redirect to a file named id.
    Reasoning

    The flagged 'redirect' is the shell-style placeholder `<id>` inside a URL in a sample command (`$ websockets wss://<app>-<user>-<id>.koyeb.app/`). It is a documentation placeholder, not a file redirect. The scanner misread the angle brackets as input redirection from a file named `id`. No file is written and nothing is shadowed.

  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.95 Documentation placeholder `<id>` in a websockets URL was misparsed as a shell redirect to a file named id.
    Reasoning

    Same false positive: `<id>` is a placeholder in a URL in a documented example command, and the AST pass read it as a redirect. No command shadowing 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.95 Documentation placeholder `<id>` in a websockets URL was misparsed as a shell redirect to a file named id.
    Reasoning

    Same false positive: the `<id>` URL placeholder in a documented example is parsed as a redirect. Nothing is written to a path that matches a system command.

  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.95 Documentation placeholder `<id>` in a websockets URL was misparsed as a shell redirect to a file named id.
    Reasoning

    The `<id>` in the URL is a template placeholder in a documented example command. The AST pass treated the angle brackets as a shell redirect to a file named id, which is a parser misreading of documentation text.

  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 Runs a fixed git rev-parse command through the shell in an experiment script.
    Reasoning

    The command is a fixed literal git invocation with shell=True. This is unnecessary shell use in an experiment script, not obfuscation or malicious intent.

  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.80 Module-level exec of the package's own version.py during setup.
    Reasoning

    This is a known websockets packaging idiom that execs the repository's own version.py to read __version__ without importing the package. The file path is fixed and in-repo, so the exec is legitimate.

  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:
    dismissed LLM, confidence 0.90 Calls os.system with an empty string on Windows to enable terminal color handling.
    Reasoning

    os.system("") on Windows is a well-known trick to enable ANSI escape processing in the console. It runs no real command.

  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:
    dismissed LLM, confidence 0.90 Creates a zlib decompressor for permessage-deflate WebSocket compression.
    Reasoning

    zlib.decompressobj is the standard RFC 7692 permessage-deflate implementation for WebSocket frames. Decompressing received payloads is the library's core purpose, not a hidden 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 Dynamic __import__ used to resolve a relative module path for a named attribute.
    Reasoning

    This is a compatibility helper that resolves a relative import by name with __import__, a standard pattern for lazy or relative module loading. The source is a module path, not untrusted data.

  20. info warn → info by review dynamic-execution tests/asyncio/server.py:26: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.70 Test server evaluates client-sent expressions with eval to return values.
    Reasoning

    This is a test fixture that evals expressions received over the websocket. That is dangerous if deployed, but it is in tests and reflects a deliberate test harness design rather than hidden malware.

  21. info warn → info by review dynamic-execution tests/asyncio/test_server.py:356: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.80 Test evaluates a latency value received from the server.
    Reasoning

    The eval parses a latency value the test server returns as a string. It is test-only code with no evidence of malicious intent.

  22. info warn → info by review dynamic-execution tests/asyncio/test_server.py:360: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.80 Test evaluates a server-returned latency string.
    Reasoning

    Same test pattern as the neighboring finding: eval of a server-reported latency number inside a unit test.

  23. info warn → info by review dynamic-execution tests/asyncio/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.assertEqual(latency, 0)
    likely benign LLM, confidence 0.80 Test evaluates a server-returned latency string.
    Reasoning

    Same test pattern: eval of a server-reported latency value in a test case.

  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.
    dismissed LLM, confidence 0.90 Round-trip repr test that evals the repr of a local extension object.
    Reasoning

    eval(repr(extension)) is a round-trip repr test on a local object the test itself constructs. No external input is involved.

  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.85 Legacy test evaluates a close_timeout value received from the server.
    Reasoning

    The eval parses a close_timeout value returned by the test's own server, in a legacy test. It is test-only code with no sign of 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 eval of a value received from the test client's websocket inside a legacy client/server test.
    Reasoning

    The eval argument comes from the test's own client over a local test websocket connection, and the result is checked with assertEqual. This is a test fixture evaluating its own controlled values, not an attack.

  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 eval of a received close-timeout value in a legacy websocket test.
    Reasoning

    Same pattern as the sibling finding: eval of a locally received test value compared against an expected number in a test assertion.

  28. info warn → info by review dynamic-execution tests/sync/server.py:28: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 Test server that evals expressions received from its test client over websocket.
    Reasoning

    This is a test server that evals each message received over a websocket. That is poor practice on a network-facing socket, but it is a localhost test fixture whose clients are the library's own tests sending expressions such as ws.latency. The raw eval is not a supply-chain or obfuscation indicator.

  29. info warn → info by review dynamic-execution tests/sync/test_server.py:256: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 eval of a latency response from the test server in a sync websocket test.
    Reasoning

    The test sends the fixed string 'ws.latency' and evals the reply, which is a numeric latency asserted in the test. Expected test-harness behaviour.

  30. info warn → info by review dynamic-execution tests/sync/test_server.py:260: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 eval of a latency reply in a sync websocket test.
    Reasoning

    Same as the sibling sync test: eval of a controlled latency reply followed by a positive-value assertion.

  31. info warn → info by review dynamic-execution tests/sync/test_server.py:269: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.90 eval of a latency reply in a sync websocket test.
    Reasoning

    Same pattern again: eval of the test server's latency reply, asserted to be zero in a test.

  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:36: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 Async trio test server that evals expressions sent by its test client.
    Reasoning

    Async counterpart of the sync test server: it evals messages from the test client over a local websocket. Poor practice in a network-facing context, but confined to a test fixture with no obfuscation or malicious indicators.

  42. info warn → info by review dynamic-execution tests/trio/test_server.py:355: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.90 eval of a latency reply in a trio websocket test.
    Reasoning

    The test sends 'ws.latency' and evals the reply, which is asserted numerically. Standard test-harness code.

  43. info warn → info by review dynamic-execution tests/trio/test_server.py:359: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 eval of a latency reply in a trio websocket test.
    Reasoning

    Same as the sibling trio test: eval of a controlled latency value followed by a positive assertion.

  44. info warn → info by review dynamic-execution tests/trio/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)
    likely benign LLM, confidence 0.90 eval of a latency reply in a trio websocket test.
    Reasoning

    Same pattern: eval of the test server's latency reply, asserted to be zero.

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.0.0
git clone https://github.com/python-websockets/websockets websockets && cd websockets
git checkout 17.2
disclude scan --severity info --diff 17.1 .