Skip to content

Commit 949cc2d

Browse files
danielmillerpclaude
andcommitted
fix(agent_api_keys): require provided secret for Slack triggers
Slack signs requests with the app's existing Signing Secret, not a per-webhook secret we can generate. Auto-generating one for a Slack trigger stored a random value that would never match, so every real Slack delivery failed signature verification. Now Slack requires the caller to supply 'secret'; GitHub still auto-generates. Addresses Greptile P1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cc9f3f4 commit 949cc2d

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

agentex/src/api/routes/agent_api_keys.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@ async def create_webhook_trigger(
141141
detail=f"A {request.source} webhook key named '{request.name}' already exists for this agent.",
142142
)
143143

144+
# GitHub lets you supply (and we can generate) a per-webhook secret to paste into the
145+
# repo's Secret field. Slack is different: it signs every request with the app's own
146+
# Signing Secret, so the caller must supply that exact value — a generated one would
147+
# never match, and validate_slack_delivery_webhook would reject every real delivery.
148+
# (See PR #329 discussion.)
149+
if request.source == AgentAPIKeyType.SLACK and not request.secret:
150+
raise HTTPException(
151+
status_code=400,
152+
detail=(
153+
"Slack triggers must supply 'secret' set to the Slack app's Signing Secret "
154+
"(from your app credentials); it can't be generated."
155+
),
156+
)
157+
144158
secret = request.secret or secrets.token_hex(32)
145159
agent_api_key_entity = await agent_api_key_use_case.create(
146160
agent_id=agent.id,

agentex/tests/unit/api/test_webhook_trigger.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,25 @@ async def test_rejects_non_webhook_source(self):
9696
authorization_service=MagicMock(),
9797
)
9898
assert exc.value.status_code == 400
99+
100+
async def test_slack_without_secret_rejected(self, monkeypatch):
101+
# Slack signs with the app's existing Signing Secret — we can't generate one,
102+
# so omitting it must 400 rather than store a random value that never matches.
103+
req = CreateWebhookTriggerRequest(
104+
agent_name="a", source=AgentAPIKeyType.SLACK, name="my-app", forward_path="slack"
105+
)
106+
with pytest.raises(HTTPException) as exc:
107+
await self._call(monkeypatch, req)
108+
assert exc.value.status_code == 400
109+
110+
async def test_slack_with_provided_secret_ok(self, monkeypatch):
111+
req = CreateWebhookTriggerRequest(
112+
agent_name="a",
113+
source=AgentAPIKeyType.SLACK,
114+
name="my-app",
115+
forward_path="slack",
116+
secret="slack-signing-secret",
117+
)
118+
resp, akuc = await self._call(monkeypatch, req)
119+
assert resp.secret == "slack-signing-secret"
120+
assert akuc.create.await_args.kwargs["api_key"] == "slack-signing-secret"

0 commit comments

Comments
 (0)