Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
13f3805
fix(slack): truncate inflated original_text in approval/confirm chat.…
liuhao1024 Jun 27, 2026
d34f955
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
f8d0e02
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
bf0339e
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
510dbbc
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
790f540
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
f133bf1
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 27, 2026
cb23e86
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
970c7eb
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
3bcc3f1
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
1277676
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
316bb86
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
a5f2f51
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
52bedf8
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
aec6eae
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
37a8400
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
5828ed6
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
4224094
Merge branch 'main' into liuhao/cron-bugfix-53693-slack-approval-trun…
liuhao1024 Jun 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions plugins/platforms/slack/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3262,6 +3262,11 @@ async def _handle_slash_confirm_action(self, ack, body, action) -> None:
original_text = block.get("text", {}).get("text", "")
break

# Slack re-escapes HTML entities in the interaction payload
# (< → &lt;, > → &gt;, & → &amp;), which can inflate the text
# past the 3000-char section-block limit on chat.update.
original_text = original_text[:3000]

updated_blocks = [
{
"type": "section",
Expand Down Expand Up @@ -3384,6 +3389,11 @@ async def _handle_approval_action(self, ack, body, action) -> None:
original_text = block.get("text", {}).get("text", "")
break

# Slack re-escapes HTML entities in the interaction payload
# (< → &lt;, > → &gt;, & → &amp;), which can inflate the text
# past the 3000-char section-block limit on chat.update.
original_text = original_text[:3000]

updated_blocks = [
{
"type": "section",
Expand Down
65 changes: 65 additions & 0 deletions tests/gateway/test_slack_approval_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,37 @@ async def test_deny_action(self):
update_kwargs = mock_client.chat_update.call_args[1]
assert "Denied by alice" in update_kwargs["text"]

@pytest.mark.asyncio
async def test_truncates_inflated_original_text(self):
"""Interaction payload re-escapes HTML entities; text must be capped."""
adapter = _make_adapter()
_attach_auth_runner(adapter)
adapter._approval_resolved["1.2"] = False

# Simulate Slack re-escaping: original was ~2990 chars, but & → &amp;
# etc. inflates it past 3000.
inflated_text = "a" * 2990 + "&amp;" * 10 # 2990 + 50 = 3040 chars

ack = AsyncMock()
body = {
"message": {"ts": "1.2", "blocks": [
{"type": "section", "text": {"type": "mrkdwn", "text": inflated_text}},
]},
"channel": {"id": "C1"},
"user": {"name": "alice", "id": "U_ALICE"},
}
action = {"action_id": "hermes_approve_once", "value": "session-key"}

mock_client = adapter._team_clients["T1"]
mock_client.chat_update = AsyncMock()

with patch("tools.approval.resolve_gateway_approval", return_value=1):
await adapter._handle_approval_action(ack, body, action)

update_kwargs = mock_client.chat_update.call_args[1]
section_text = update_kwargs["blocks"][0]["text"]["text"]
assert len(section_text) <= 3000

@pytest.mark.asyncio
async def test_global_allowlist_blocks_unauthorized_click(self, monkeypatch):
adapter = _make_adapter()
Expand Down Expand Up @@ -346,6 +377,40 @@ async def test_global_allowlist_allows_authorized_click(self, monkeypatch):
mock_client.chat_update.assert_called_once()
mock_client.chat_postMessage.assert_called_once()

@pytest.mark.asyncio
async def test_truncates_inflated_original_text(self):
"""Interaction payload re-escapes HTML entities; text must be capped."""
adapter = _make_adapter()
_attach_auth_runner(adapter)
adapter._approval_resolved["2222.3333"] = False

# Simulate Slack re-escaping inflating text past 3000 chars.
inflated_text = "b" * 2990 + "&lt;" * 10 # 2990 + 40 = 3030 chars

ack = AsyncMock()
body = {
"message": {"ts": "2222.3333", "blocks": [
{"type": "section", "text": {"type": "mrkdwn", "text": inflated_text}},
]},
"channel": {"id": "C1"},
"user": {"name": "owner", "id": "U_OWNER"},
}
action = {
"action_id": "hermes_confirm_once",
"value": "agent:main:slack:group:C1:1111|confirm-1",
}

mock_client = adapter._team_clients["T1"]
mock_client.chat_update = AsyncMock()
mock_client.chat_postMessage = AsyncMock()

with patch("tools.slash_confirm.resolve", new=AsyncMock(return_value="ok")):
await adapter._handle_slash_confirm_action(ack, body, action)

update_kwargs = mock_client.chat_update.call_args[1]
section_text = update_kwargs["blocks"][0]["text"]["text"]
assert len(section_text) <= 3000


# ===========================================================================
# _fetch_thread_context
Expand Down
Loading