Skip to content

feat(webhooks): run organization triggers as their creator - #8423

Merged
eshurakov merged 5 commits into
mainfrom
kilo/great-prism-ud0
Oct 22, 2026
Merged

eshurakov merged 5 commits into
mainfrom
kilo/great-prism-ud0

Conversation

@eshurakov

@eshurakov eshurakov commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add an opt-in Run as webhook creator mode for new organization Cloud Agent triggers, including scheduled triggers. Existing triggers keep using the organization webhook bot.
  • The server binds the execution identity to the authenticated creator. Organization billing and scope stay the same, and the execution identity cannot be changed after creation. Only the creator can directly edit or Run now a creator-run trigger.
  • Creator-run webhooks require shared-secret auth with at least 16 characters. The creator account, the organization and the creator's direct membership are revalidated before every delivery and every initiation retry. Known authorization failures fail promptly and never fall back to the bot. Transient infrastructure failures keep bounded retries.
  • The nullable execution identity is stored through an additive Durable Object SQLite migration. No shared PostgreSQL migration is needed.

Webhook security hardening (review follow-up)

  • Scheduled triggers can no longer be run through the public inbound URL. Scheduled triggers have no webhook auth, but /inbound still captured requests for them. Anyone who knew the trigger URL could run a scheduled trigger with an attacker-controlled body as its principal: the creator for creator-run triggers, or the user for personal triggers. TriggerDO.captureRequest now rejects scheduled triggers with 404 and does not write anything. Scheduled triggers now run only from the DO alarm or the internal-key-authenticated /invoke route. The UI never showed an inbound URL for scheduled triggers.
  • The internal API key is compared in constant time.
  • Prompt templates are rendered in a single pass. Placeholders inside the webhook body or query, such as {{headers}}, are no longer expanded.
  • If the worker no longer has an organization trigger, update and Run now clean up the orphaned PostgreSQL row again.

Behavior And Trust Model

  • This is a create-time choice. Existing webhooks must be recreated to change execution identity, because the original creator was never recorded.
  • Organization profiles and repository settings stay shared and are resolved at run time, as for manual organization runs. The opt-in UI warns that members who can edit those settings can affect runs under the creator's identity. This is not a frozen or creator-private environment.
  • Personal and KiloClaw triggers cannot use this mode. Access inherited from a parent organization, or elevated admin access without direct membership, cannot enable it.
  • Other members can still delete a creator-run trigger. Deleting only stops it, so this cannot escalate access.
  • MCP exposure: webhookTriggers.create with runAsCreator is intentionally callable from MCP. It is equivalent to personal triggers, which MCP can already create and which mint the calling user's token on every run. The identity always comes from the authenticated caller, so a caller cannot target another user. Organization credit use is the same as for bot-run org triggers, which MCP can already create.
  • The UI uses the existing Cloud controls and Cloud overlay/interaction-quality guidance. There is no dedicated form recipe for execution identity.

Verification

  • Worker unit suite: 153 passed.
  • Workers-runtime integration suite: 59 passed. This includes migration defaults, SQLite persistence, immutable identity, auth requirements, rejected public capture of personal and creator-run scheduled triggers, creator capture auth with redaction, and secret strength checks on create and rotation.
  • Focused web router, form, edit, invoke-dialog, table, hook and client suites: 118 passed. For these mocked suites, only the database lifecycle setup was replaced.
  • Worker typecheck and lint passed. Full web tsgo --noEmit passed. Lint passed on the changed web files. Formatting and git diff --check passed.
  • The normal web Jest setup could not run because PostgreSQL is unavailable in the sandbox. Live end-to-end execution and an actual billing deduction were not tested.

Comment thread services/kilo-mcp/catalog.json Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit replaces the creator-mode checkbox with a Select picker, exposes the creator display label from useSession, and updates the tests; the changed lines introduce no new bugs or security issues.

Assumptions: [INFERENCE] next-auth useSession is available on the webhook create/edit pages via the global Providers/SessionProvider wrapper, and session.user.id uses the same Kilo user id as the server's executionUserId.

Files Reviewed (2 files)
  • apps/web/src/components/webhook-triggers/TriggerForm.tsx
  • apps/web/src/components/webhook-triggers/TriggerForm.test.ts
Previous Review Summaries (2 snapshots, latest commit 06d797a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 06d797a)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commits close the previously reported scheduled-trigger inbound exposure, enforce the 16-character shared secret for creator-run webhooks on create and rotation, make the internal key comparison constant-time, render prompt templates in a single pass, and restore orphan cleanup — no new issues found in the incremental diff.

Files Reviewed (12 files)
  • apps/web/src/components/webhook-triggers/TriggerForm.test.ts
  • apps/web/src/components/webhook-triggers/TriggerForm.tsx
  • apps/web/src/lib/webhook-trigger-validation.ts
  • apps/web/src/routers/webhook-triggers-router.schema.test.ts
  • apps/web/src/routers/webhook-triggers-router.ts
  • services/webhook-agent-ingest/src/dos/TriggerDO.ts
  • services/webhook-agent-ingest/src/util/auth.test.ts
  • services/webhook-agent-ingest/src/util/auth.ts
  • services/webhook-agent-ingest/src/util/prompt-template.test.ts
  • services/webhook-agent-ingest/src/util/prompt-template.ts
  • services/webhook-agent-ingest/src/util/webhook-auth.ts
  • services/webhook-agent-ingest/test/integration/trigger-do.test.ts

Previous review (commit 0ec006b)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

Scheduled creator-run triggers are publicly invocable through the unauthenticated inbound webhook route, bypassing the new shared-secret requirement and running under the creator's identity.

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

CRITICAL

File Line Issue
services/webhook-agent-ingest/src/routes/api.ts 216 Scheduled creator-run triggers skip webhook auth but are still reachable via the public /inbound route, allowing unauthenticated runs as the creator.

WARNING

File Line Issue
services/kilo-mcp/catalog.json 36532 runAsCreator is a new MCP-visible create parameter that binds future runs to the caller's identity and spends org credits; the PR does not justify the MCP exposure.

SUGGESTION

File Line Issue
apps/web/src/routers/webhook-triggers-router.ts 660 Early worker-404 return skips cleanupOrphanDbRecord, leaving a stale Postgres row that blocks re-creating the trigger id.
Files Reviewed (23 files)
  • apps/web/src/app/(app)/cloud/webhooks/[triggerId]/EditWebhookTriggerContent.tsx
  • apps/web/src/app/(app)/cloud/webhooks/new/CreateWebhookTriggerContent.tsx
  • apps/web/src/components/webhook-triggers/TriggerForm.test.ts
  • apps/web/src/components/webhook-triggers/TriggerForm.tsx
  • apps/web/src/lib/webhook-agent/webhook-agent-client.ts
  • apps/web/src/routers/webhook-triggers-router.schema.test.ts
  • apps/web/src/routers/webhook-triggers-router.ts - 1 issue
  • services/kilo-mcp/catalog.json - 1 issue
  • services/webhook-agent-ingest/drizzle/0005_open_morph.sql
  • services/webhook-agent-ingest/drizzle/meta/0005_snapshot.json
  • services/webhook-agent-ingest/drizzle/meta/_journal.json
  • services/webhook-agent-ingest/drizzle/migrations.js
  • services/webhook-agent-ingest/src/db/queries.ts
  • services/webhook-agent-ingest/src/db/sqlite-schema.migration.test.ts
  • services/webhook-agent-ingest/src/db/sqlite-schema.ts
  • services/webhook-agent-ingest/src/dos/TriggerDO.ts
  • services/webhook-agent-ingest/src/queue-consumer.test.ts
  • services/webhook-agent-ingest/src/queue-consumer.ts
  • services/webhook-agent-ingest/src/routes/api.test.ts
  • services/webhook-agent-ingest/src/routes/api.ts - 1 issue
  • services/webhook-agent-ingest/src/services/token-minting-service.test.ts
  • services/webhook-agent-ingest/src/services/token-minting-service.ts
  • services/webhook-agent-ingest/test/integration/trigger-do.test.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 97.2K · Output: 13K · Cached: 929K

Review guidance: REVIEW.md from base branch main

eshurakov and others added 3 commits October 21, 2026 13:10
Compare the internal API key in constant time and render prompt templates in a
single pass so placeholders inside webhook request data are never expanded.
Scheduled triggers have no webhook auth, but the public inbound route still
captured requests for them, so anyone with the trigger URL could run a
creator-run (or personal) scheduled trigger as its principal. Inbound capture
now rejects scheduled triggers; they run only from alarms or authenticated
invokes.

Also restrict Run now on creator-run triggers to the creator, require a
16-character minimum secret for creator-run webhooks, and restore orphan
PostgreSQL cleanup when the worker no longer has an organization trigger.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants