feat(triggers): external webhook trigger for agent dispatch - #10
Merged
Conversation
New internal trigger internal:external-webhook: any third-party system can
dispatch an agent by POSTing to the router at
/external/webhook/:projectId/:agentType with Authorization: Bearer <password>.
- Password is the EXTERNAL_WEBHOOK_PASSWORD_<AGENT_TYPE> project credential
(org-credential inheritance applies). Authentication fails closed: no
stored password rejects every request with 403; comparison is timing-safe.
- POST body (capped at 64 KiB) reaches the agent as trigger context via the
manual-run path's triggerCommentBody. Dispatch reuses createQueuedRun +
submitDashboardJob({type:'manual-run'}) — no new job variant, adapter, or
trigger handler. Runs carry triggerType 'external-webhook'.
- Enablement enforced end-to-end: agent enabled in project + trigger enabled
(getResolvedTriggerConfig); unknown/undeclared/disabled all return 404
(anti-probing) with distinct decision reasons in webhook_logs
(source 'external'; Authorization header never logged).
- Declared on 7 agents (implementation, planning, splitting, backlog-manager,
review, resolve-conflicts, alerting), defaultEnabled false.
- UI: Agents → agent → Triggers tab shows the webhook URL (copy button +
curl example) and password field when the trigger is enabled, via a new
renderTriggerExtra slot on DefinitionTriggerToggles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses all confirmed review findings plus one verified manually: - CRITICAL: the POST body never reached the 7 declaring agents' prompts (only respond-to-* task prompts render commentBody). New appendExternalTriggerRequest appends the payload to the rendered task prompt centrally, gated on triggerType === 'external-webhook'. - HIGH: EXTERNAL_WEBHOOK_PASSWORD_* credentials are inbound-auth verifiers and are now excluded from worker container env injection — a prompt-injected agent could otherwise read the password and gain a self-re-dispatch primitive (org-level passwords escalated cross-project). - Brute-force resistance: failed attempts are rate-limited per client IP via the shared sliding-window limiter (429 + Retry-After; successful auth resets the counter), and webhook passwords now require >=16 chars at both credential write paths (project + org). - Anti-enumeration: unknown project, unset password, and wrong token all return an identical generic 401; distinct decision reasons live only in webhook_logs. - Bearer scheme matched case-insensitively (RFC 7235). - Body read incrementally with the 64 KiB cap instead of buffering first. - Enqueue failures return a generic message; detail stays in logs. - internal:external-webhook added to TRIGGER_REGISTRY (definition editor). - Webhook URL display no longer falls back to the dashboard API_URL (wrong service) — router URL comes from WEBHOOK_CALLBACK_BASE_URL, with a dev-only origin swap, else an explicit placeholder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New internal trigger
internal:external-webhook: any third-party system can dispatch an agent by POSTing to the router:The POST body (≤64 KiB) reaches the agent as its work request — appended to the rendered task prompt as an
## External trigger requestsection (central, gated on the trigger type). Dispatch rides the existing manual-run dashboard-job path; runs appear with triggerexternal-webhook.Configuration
defaultEnabled: false).EXTERNAL_WEBHOOK_PASSWORD_<AGENT_TYPE>project credential — org-credential inheritance applies, so one org-level password can arm all projects. Server enforces ≥16 chars.Security (adversarially reviewed, all confirmed findings fixed)
external; Authorization header never logged).EXTERNAL_WEBHOOK_PASSWORD_*keys are excluded from worker container env injection — agents can never read the inbound-auth verifier (prevents prompt-injection → self-re-dispatch, incl. cross-project via org-level passwords).Notes
Testing
11,254 unit tests green. New: endpoint matrix (fail-closed, anti-enumeration triple, rate limit + reset, case-insensitive Bearer, body cap, generic 500, log hygiene), worker-env exclusion, prompt-append gating, password policy, helper key mapping, YAML declaration guard.
🤖 Generated with Claude Code