Commit 144122a
fix(code-mode): address Patch76 round-3 review (H1, M2, M3, L1-L6) + clean uv.lock
Rebased on current upstream/master (was 8 commits behind) and
regenerated uv.lock so it adds only ``pydantic-monty==0.0.9`` —
the prior lockfile carried 8 unrelated package downgrades that the
fresh resolver pass clears (M1).
H1 — entity_registry/remove + device_registry/remove_config_entry
-----------------------------------------------------------------
The blocklist was using ``config/entity_registry/delete`` and
``config/device_registry/delete`` — neither is a registered HA Core
WS command. Verified ha-mcp itself emits the actually-registered
names: ``tools_entities.py:1130`` uses ``config/entity_registry/remove``
and ``tools_registry.py:753`` uses
``config/device_registry/remove_config_entry``. A sandbox
``ws_send({"type": "config/entity_registry/remove", ...})`` was
slipping past the blocklist and bypassing ``ha_remove_entity``'s
wrapping checks.
Added both real names to ``_BLOCKED_WS_COMMANDS``. Left the dead
``*_registry/delete`` strings as-is — over-blocking inert names is
harmless, under-blocking real ones is the bug.
M2 — percent-encoded ``..`` traversal
-------------------------------------
The segment-loop check at ``_normalize_endpoint`` rejects literal
``..`` segments but treats ``%2e%2e`` as a normal segment. httpx
itself doesn't decode at the transport level, but reverse proxies
(nginx with config drift, traefik with custom routers) sometimes
decode-then-resolve, which would let ``%2e%2e/auth/providers``
escape ``/api/`` server-side. Each segment is now passed through
``urllib.parse.unquote`` before the ``..`` comparison so encoded
forms can't slip past.
M3 — naked list returns from ``_extract_tool_result``
-----------------------------------------------------
The basic-types tuple at line 566 didn't include ``list``, so a
tool returning a plain ``[{"id": 1}, ...]`` fell into the
ToolResult-extraction branch, found no ``.text`` on its dict
elements, and got string-repr'd. Sandbox code ended up with
``"[{'id': 1}, ...]"`` instead of an iterable list.
Lists are now passed through when they don't look like FastMCP's
content-block shape (heuristic: first element has ``.text`` or
``.type`` — the two attributes content blocks always carry). Plain
data lists pass straight to the sandbox.
L1 — sandbox-side error shape contract documented
--------------------------------------------------
Patch76 noted that sandbox code sees two error shapes
(``{"error": str}`` from the bridge helpers, ``{"success": False,
"error": {"code", "message"}}`` from ``_sandbox_error``) and
suggested standardizing on the structured shape. Standardizing
that direction would have changed ~15 existing tests that do
``result.get("error", "").lower()`` blindly. The simpler
``{"error": str}`` shape is what most helpers and tests already
assume, and the structured form has real value for ``call_tool``
(propagates the underlying tool's ``ErrorCode``).
Resolved instead by making the contract explicit in the
``_run_sandboxed_code`` docstring: both shapes are documented,
consumers should always probe with ``if "error" in result:``
before indexing. Same UX outcome (consumers know what to
expect) without churning the tests.
L2 — switched ``%r`` for the justification log line
---------------------------------------------------
Removed the ``_log_safe`` helper and inlined ``%r`` (repr())
for the LLM-supplied ``justification`` log line, matching the
``%r`` already used for endpoint/type fields in the audit-log
lines. ``%r`` escapes ``\r`` / ``\n`` / ``\t`` as literal
``\r`` / ``\n`` / ``\t`` sequences in formatted output — same
log-injection prevention as ``_log_safe`` but consistent with
the rest of the file.
L4 / L5 / L6 — clarifying comments
----------------------------------
* L4: Note next to the ``_saved_tools_load_failed`` read in
``_save_saved_tools`` that the read is intentionally without
``global`` — Python doesn't require it for read-only access.
* L5: One-line clarification in ``_normalize_endpoint``'s
docstring that ``@`` later in the path (``events/foo@bar``)
is acceptable — only userinfo position (before the first
``/``) is the credential-leaking shape.
* L6: Block comment on the ``_saved_tools`` module-level
declaration explaining *why* it's at module level
(cross-call persistence is the documented contract;
per-request scope wouldn't allow ``run_saved`` to see prior
``save_as`` writes), and pointing at
``code_mode_saved_tools_path`` as the persistence boundary.
Test expansion — full ``_BLOCKED_WS_COMMANDS`` parametrize
----------------------------------------------------------
``test_ws_send_blocks_command`` now parametrizes over all 25
entries in ``_BLOCKED_WS_COMMANDS`` (was: 3 hand-picked cases).
The "blocklist names a command HA Core doesn't accept" class
of bug surfaces in CI now: if the blocklist drops an entry,
the corresponding parametrize row fails; if HA Core renames a
command, the test fails with the now-stale name still in the
parametrize list and a maintainer notices.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 80c424a commit 144122a
3 files changed
Lines changed: 204 additions & 129 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
42 | 54 | | |
43 | 55 | | |
44 | 56 | | |
45 | | - | |
46 | | - | |
47 | 57 | | |
48 | 58 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | 59 | | |
71 | 60 | | |
72 | 61 | | |
| |||
179 | 168 | | |
180 | 169 | | |
181 | 170 | | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
182 | 176 | | |
183 | 177 | | |
184 | 178 | | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
185 | 183 | | |
186 | 184 | | |
187 | 185 | | |
| |||
497 | 495 | | |
498 | 496 | | |
499 | 497 | | |
| 498 | + | |
| 499 | + | |
500 | 500 | | |
501 | 501 | | |
502 | 502 | | |
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
559 | 571 | | |
560 | 572 | | |
561 | 573 | | |
| |||
615 | 627 | | |
616 | 628 | | |
617 | 629 | | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
618 | 642 | | |
619 | 643 | | |
620 | 644 | | |
| |||
641 | 665 | | |
642 | 666 | | |
643 | 667 | | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
644 | 673 | | |
645 | 674 | | |
646 | 675 | | |
647 | 676 | | |
648 | 677 | | |
649 | 678 | | |
650 | | - | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
651 | 683 | | |
652 | 684 | | |
653 | 685 | | |
| |||
666 | 698 | | |
667 | 699 | | |
668 | 700 | | |
669 | | - | |
670 | | - | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
671 | 704 | | |
672 | | - | |
| 705 | + | |
673 | 706 | | |
674 | | - | |
| 707 | + | |
| 708 | + | |
675 | 709 | | |
676 | 710 | | |
677 | 711 | | |
| |||
1169 | 1203 | | |
1170 | 1204 | | |
1171 | 1205 | | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
1172 | 1212 | | |
1173 | | - | |
1174 | | - | |
| 1213 | + | |
| 1214 | + | |
1175 | 1215 | | |
1176 | 1216 | | |
1177 | 1217 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1410 | 1410 | | |
1411 | 1411 | | |
1412 | 1412 | | |
1413 | | - | |
1414 | | - | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
1415 | 1449 | | |
1416 | | - | |
1417 | | - | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
1418 | 1458 | | |
1419 | 1459 | | |
1420 | | - | |
| 1460 | + | |
1421 | 1461 | | |
1422 | | - | |
1423 | | - | |
1424 | | - | |
1425 | | - | |
1426 | | - | |
1427 | | - | |
1428 | | - | |
1429 | | - | |
1430 | | - | |
1431 | | - | |
1432 | | - | |
1433 | | - | |
1434 | | - | |
1435 | | - | |
1436 | | - | |
1437 | | - | |
1438 | | - | |
1439 | | - | |
1440 | | - | |
1441 | | - | |
1442 | | - | |
1443 | | - | |
1444 | | - | |
1445 | | - | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
1446 | 1481 | | |
1447 | 1482 | | |
1448 | 1483 | | |
| |||
0 commit comments