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
285 scanned, 67 skipped (binary, oversized, or unrecognised)
Signals
1 critical · 0 warn · 43 info
Scan time
28.0 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-20 03:26

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)
    suspicious LLM, confidence 0.55 Runtime-built command with shell=True where a SHA from commit data is substituted into a URL template.
    Reasoning

    The command is built by replacing ':sha' in a COMMIT_API template with a value popped from a todo list fed by API results, then run with shell=True. That is a real shell-injection risk if the SHA or API data is untrusted, but the snippet shows a plain experiment script fetching commits, with no sign of malicious intent or obfuscation. It is a code-quality issue more than a malware indicator.

  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.80 Non-breaking space in the indentation of a git commit output example in Heroku deployment docs.
    Reasoning

    The flagged U+00A0 sits in the indentation of a pasted git terminal transcript inside an RST code block. Non-breaking spaces are a common artifact of copying from web pages or terminals, and the surrounding text is ordinary git output with no executable payload.

  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.80 Non-breaking space in the indentation of a git output transcript in Heroku docs.
    Reasoning

    Same pattern as the adjacent finding: a U+00A0 in the indentation of a git push/commit transcript in documentation. It is a formatting artifact with no code execution or hidden logic.

  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.80 Non-breaking space in indentation of git 'create mode' output in Heroku docs.
    Reasoning

    The line is a plain 'create mode' git output line inside a documentation code block, with a non-breaking space in the indentation. This is consistent with copy-paste formatting rather than an obfuscated payload.

  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.80 Non-breaking space in indentation of git output in Heroku docs.
    Reasoning

    A non-breaking space in the indentation of a git output example. The raw pass flags whitespace only, and the content is benign documentation 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.80 Non-breaking space in indentation of a git push transcript in Heroku docs.
    Reasoning

    The line is a git push output transcript ('To https://git.heroku.com/...') with a non-breaking space in the indentation. This is a documentation formatting artifact.

  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.80 Non-breaking space in indentation of git commit output in Koyeb docs.
    Reasoning

    A non-breaking space in the indentation of a git commit output example in Koyeb deployment docs. Consistent with copy-paste artifacts in documentation, with no executable content.

  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.80 Non-breaking space in indentation of git output in Koyeb docs.
    Reasoning

    Non-breaking space in the indentation of git output lines in a documentation block. The content is benign transcript text.

  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.80 Non-breaking space in indentation of git output in Koyeb docs.
    Reasoning

    Non-breaking space in the indentation of git 'create mode' lines in a docs code block. This is a formatting artifact, not obfuscation.

  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.80 Non-breaking space in indentation of git output in Koyeb docs.
    Reasoning

    Non-breaking space in indentation of git output lines inside documentation. The raw pass flags whitespace only and the content is ordinary transcript text.

  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 Placeholder '<id>' in a Koyeb URL example was misparsed as a shell redirect to a file named 'id'.
    Reasoning

    The 'redirect to id' is an artifact of the placeholder '<id>' in the URL 'wss://<app>-<user>-<id>.koyeb.app/' inside a shell-like example. The AST pass read the angle brackets as redirection, so there is no real write to a file named 'id' and no PATH hijack. The snippet is a websockets session transcript.

  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 Placeholder '<id>' in a Koyeb websockets example misread as a redirect target.
    Reasoning

    Same false positive: the '<id>' placeholder in a websockets URL example is parsed as a redirect. The snippet shows an interactive client session, not a command that writes to a system-command path.

  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 Placeholder '<id>' in a Koyeb websockets example misread as a redirect target.
    Reasoning

    Identical pattern: angle-bracket placeholder '<id>' inside a URL in a documented websockets transcript, misclassified as a redirect to a file named 'id'.

  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 Placeholder '<id>' in a tutorial URL example misread as a redirect to 'id'.
    Reasoning

    The '<id>' template placeholder in the URL is parsed as a redirect by the AST pass. The surrounding transcript is documentation of a websockets client session with no file write or PATH manipulation.

  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 subprocess call with shell=True running a literal git rev-parse command.
    Reasoning

    The command is a fixed string literal, 'git rev-parse origin/main', so there is no injection surface. shell=True is unnecessary but harmless here, and the call is ordinary repository tooling.

  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 project's own version.py to read __version__ during packaging.
    Reasoning

    The exec reads src/websockets/version.py from the project's own tree to obtain the version string, a common setup.py idiom. The surrounding code is ordinary packaging logic, so the flagged non-literal exec is benign.

  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.95 Calls os.system('') on Windows to enable ANSI terminal color support.
    Reasoning

    The literal os.system call with an empty string is the well-known Windows trick to enable ANSI escape handling in the console. It runs no attacker-controlled 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:
    likely benign LLM, confidence 0.95 Creates a zlib decompressor to implement WebSocket permessage-deflate decompression.
    Reasoning

    zlib.decompressobj is the standard permessage-deflate implementation required by RFC 7692 for decompressing WebSocket frame payloads. There is no embedded payload; the data comes from the peer protocol stream.

  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.90 Dynamically imports a (possibly relative) module by name for lazy loading.
    Reasoning

    This is a lazy_import helper that resolves a module name from the caller's package and relative level, a standard pattern for deferred imports. The name comes from library code, not untrusted input.

  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.75 Test echo server that evals client-sent expressions to test remote evaluation.
    Reasoning

    This is a test fixture server that evals messages received over the socket to return values for latency tests. Evaluating remote input is unsafe if deployed, but here it is an intentional test-only helper with no production role.

  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 Evaluates the test server's latency reply in an asyncio server test.
    Reasoning

    The test sends the fixed string ws.latency to the test server and evals the server's numeric reply. The eval runs only on known test responses.

  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 Evaluates the test server's latency reply in an asyncio server test.
    Reasoning

    Same pattern as the other latency test: eval of the fixed test server's numeric reply to ws.latency. No untrusted input is involved.

  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 Evaluates the test server's latency reply in an asyncio server test.
    Reasoning

    Same pattern again: eval of the test server's reply to a fixed ws.latency request, confined to the test suite.

  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-trips an extension object through repr and eval in a unit test.
    Reasoning

    The test evals repr() of the extension object it created to check round-trip equality. The input is entirely local and is a standard repr-roundtrip test.

  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 Evaluates a server-returned close_timeout value in a legacy test.
    Reasoning

    The test evals a close_timeout value that the test server itself sent back to check a numeric result. The source is the controlled test server.

  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 Evaluates a server-returned close_timeout value in a legacy test.
    Reasoning

    Same pattern: eval of a controlled numeric value returned by the test server in a legacy timeout test.

  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 Evaluates a server-returned close_timeout value in a legacy test.
    Reasoning

    Same pattern: eval of a controlled numeric value returned by the test server in a legacy timeout test.

  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.75 Sync test echo server that evals client-sent expressions for tests.
    Reasoning

    This is the sync counterpart of the asyncio test server, evaluating client-sent expressions for latency tests. It is intentionally unsafe test scaffolding and is not reachable in production code.

  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 Evaluates the test server's latency reply in a sync server test.
    Reasoning

    The test sends ws.latency to the test server and evals its numeric reply. The eval input is fixed test output.

  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 Evaluates the test server's latency reply in a sync server test.
    Reasoning

    Same pattern as the other sync latency test: eval of the controlled test server's reply to ws.latency.

  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 server-returned latency string with eval.
    Reasoning

    The eval reads a latency value the test server itself returned over a websocket. It sits in a test module that exercises the server's ws.latency feature and asserts on the result. eval of network data is a poor practice, but here it is a test-harness convenience with no sign of malicious intent.

  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.75 Trio test server evaluates client-sent expressions via eval.
    Reasoning

    This is a test fixture server that evals expressions received from a websocket client. Evaluating remote input is dangerous if deployed, but the file is a test server under tests/, and the expression handler exists to drive test scenarios. Nothing suggests a dropper or a hidden payload, though the pattern would be unsafe outside tests.

  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 Trio test evaluates the latency response with eval.
    Reasoning

    The eval evaluates the server's own latency reply in a test that asserts on the value. This is a test-only pattern in a websocket library's test suite.

  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 Trio test evaluates the latency response with eval.
    Reasoning

    Same pattern as the sibling finding: eval of the server's latency reply inside a test assertion. No malicious indicators in the snippet or file context.

  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 Trio test evaluates the latency response with eval.
    Reasoning

    Again eval of the server's latency reply within a test asserting zero latency. It is a test-harness pattern, not an attack.

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.3
disclude scan --severity info --diff 17.2 .