feat(api): Add generic webhook source for arbitrary event-driven task spawning#902
feat(api): Add generic webhook source for arbitrary event-driven task spawning#902knechtionscoding wants to merge 1 commit intokelos-dev:mainfrom
Conversation
f3ed1e9 to
957eb9b
Compare
957eb9b to
2d7e08c
Compare
There was a problem hiding this comment.
2 issues found across 17 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/manifests/charts/kelos/templates/crds/taskspawner-crd.yaml">
<violation number="1" location="internal/manifests/charts/kelos/templates/crds/taskspawner-crd.yaml:1011">
P2: CRD says fieldMapping must include an `id` key, but schema doesn’t enforce it; generic webhook handling will accept missing `id` and leave parsed.ID empty, contradicting the documented requirement.</violation>
</file>
<file name="api/v1alpha1/taskspawner_types.go">
<violation number="1" location="api/v1alpha1/taskspawner_types.go:488">
P2: XValidation does not enforce the documented “set exactly one” requirement for value/pattern; filters with neither field set pass validation.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
2d7e08c to
b9475ed
Compare
|
/kelos review |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos Reviewer Agent @gjkim42
Review Summary
Verdict: REQUEST CHANGES
Scope: Adds generic webhook source type to TaskSpawner with path-based routing, JSONPath field extraction, HMAC signature validation, and Helm chart support
Findings
Correctness
-
internal/webhook/generic_filter.go:39/internal/webhook/handler.go:482— SharedFieldsmap leaks state across spawner iterations (blocking)
ExtractFieldsadds keys toparsed.Generic.Fieldsbut never clears the map between spawner iterations. InprocessWebhook(line 357–414), the loop callsmatchesSpawner→ExtractFieldsfor each spawner. If Spawner A hasfieldMapping: {id: "$.a.id", custom_a: "$.a.val"}and Spawner B hasfieldMapping: {id: "$.b.id"}, after processing Spawner A theFieldsmap contains{id: "...", custom_a: "..."}. When Spawner B runs, it overwritesidbut leavescustom_afrom Spawner A. ThencreateTask→ExtractGenericWorkItemfor Spawner B includes the stalecustom_aas a template variable.
Fix: ResetFieldsto a fresh map at the start ofExtractFields. -
internal/webhook/handler.go:563-581—getGenericWebhookConfiglists ALL TaskSpawners on every request (non-blocking)
Every incoming generic webhook triggers aclient.Listof all TaskSpawners to determine signature/delivery headers, in addition to the existinggetMatchingSpawnersList call further down. This doubles the API server load per webhook delivery. Consider combining these into a single List, or caching the config per source with a short TTL. -
internal/webhook/handler.go:576—getGenericWebhookConfigreturns first-match only
If multiple TaskSpawners listen to the same source with differentSignatureHeaderorSignaturePrefixvalues, only the first match's config is used for validation. The second spawner's signature config is silently ignored. Worth documenting in the CRD field comments.
Tests
- Test coverage is comprehensive: parsing, field extraction, filters (AND semantics, exact/regex, nested fields, numeric/boolean coercion), path extraction, signature validation (with/without prefix), HTTP handler tests for matching, non-matching, wrong source, idempotency, and hyphenated source names. Well done.
make verifypasses — generated files (deepcopy, CRD YAML, install-crd.yaml) are up to date.make testwas still compiling from scratch in this container environment; no failures observed at the time of review.
Cubic's prior review — rebuttal
The two P2 violations flagged by cubic are both incorrect:
-
"CRD says fieldMapping must include an
idkey, but schema doesn't enforce it" — Wrong. The CRD YAML (taskspawner-crd.yaml:1084) contains an x-kubernetes-validation rule:'id' in self.fieldMapping. This enforces the requirement at the schema level. -
"XValidation does not enforce the 'set exactly one' requirement for value/pattern" — Wrong. The rule
has(self.value) != (has(self.pattern) && size(self.pattern) > 0)uses XOR semantics that correctly rejects both-omitted and both-set cases.
Security
- No hardcoded secrets — per-source secrets loaded from env vars at runtime
- HMAC-SHA256 signature validation with timing-safe comparison via existing
validateHMACSignature - Payload size limit (10 MB) inherited from existing handler
- Source name from URL path is safe:
os.Getenvtreats the string literally, no injection risk - Optional no-secret mode (skip validation) is clearly logged — appropriate for sources that don't support signing
Conventions
- Logging messages start with capital letters, no trailing punctuation ✓
- Generated files included and match
make updateoutput ✓ - PR description follows template with all sections filled ✓
workspaceRefvalidation rule intentionally excludeswebhooksource — correct per design (generic webhooks may not be git-repo-driven)
Suggestions (optional)
internal/webhook/generic_filter.go:68—regexp.MatchStringrecompiles the regex on every filter evaluation. For high-throughput sources, consider compiling once. Not blocking for v1 but worth a TODO.- Consider adding validation that prevents two spawners from claiming the same
sourcename with conflictingSignatureHeader/SignaturePrefixconfigurations. - Minor:
extractSourceFromPathrejects paths with >3 segments but returns the generic "Missing source" message. A more descriptive error could help debugging.
/kelos needs-input
b9475ed to
9ff4175
Compare
|
@gjkim42 I have implemented review notes and suggestions. Let me know if there's anything else :) |
… spawning Adds a new `webhook` source type to TaskSpawner that accepts arbitrary HTTP POST payloads and maps them to task template variables via JSONPath expressions. This enables integration with any webhook-capable system (Notion, Sentry, Drata, etc.) without Kelos-side code changes. Key design: - Path-based routing: /webhook/<source> determines both the URL and the HMAC secret env var (<SOURCE>_WEBHOOK_SECRET) - Configurable JSONPath field extraction via fieldMapping - AND-semantics filters with exact match and regex support - Plugs into the existing kelos-webhook-server with --source generic - No workspaceRef required for generic webhook spawners - Helm chart adds generic source with envFrom-based multi-secret support - If a secret doesn't exist accept json in Implements kelos-dev#687
822abfd to
187500d
Compare
|
Will take a look. Thanks for the PR! |
|
@gjkim42 bump :) |
What type of PR is this?
/kind feature
What this PR does / why we need it:
Adds a generic webhook source type (
spec.when.webhook) to TaskSpawner that accepts arbitrary HTTP POST payloads with JSON bodies and maps them to task template variables via configurable JSONPath expressions. This enables integration with any webhook-capable system (Notion, Sentry, Drata, Salesforce, Aikido, etc.) without requiring Kelos-side code changes for each new integration.Key design decisions:
/webhook/<source>) and derives its HMAC secret from the<SOURCE>_WEBHOOK_SECRETenvironment variable (e.g.,/webhook/notion→NOTION_WEBHOOK_SECRET)fieldMappingmaps JSONPath expressions to template variables available inpromptTemplateandbranchkelos-webhook-serverwith--source genericrather than creating per-spawner DeploymentsworkspaceRefsince they may not be git-repo-drivenwebhookServer.sources.genericwithenvFrom-based multi-secret injection so all per-source secrets come from a single Secret resourceWhich issue(s) this PR is related to:
Fixes #687
Special notes for your reviewer:
envFromin the helm chart (notvalueFrom) since users will have multiple<SOURCE>_WEBHOOK_SECRETkeys in one SecretSignaturePrefixfield defaults to"sha256="but can be set to""for providers that send raw hex digests (e.g., Linear-style)/webhook/as a catch-all prefix for generic, which is placed after the more specific/webhook/githuband/webhook/linearrulesgithub.com/PaesslerAG/jsonpathdependency for JSONPath evaluationDoes this PR introduce a user-facing change?
Summary by cubic
Adds a generic
webhooksource to TaskSpawner to spawn tasks from any JSON POST at/webhook/<source>using JSONPath field mapping, per‑source HMAC secrets, and optional signature validation. Enables integrations (e.g., Notion, Sentry, Drata) without Kelos code changes and addresses #687.New Features
POST /webhook/<source>with<SOURCE>_WEBHOOK_SECRET; if no secret, signature validation is skipped.fieldMappingto template vars (requiresid); also setsID,Title,Body,URL(empty defaults);.Payloadalways available.valueorpattern.SignatureHeader/SignaturePrefix(defaultsX-Webhook-Signature-256,sha256=) and optionalDeliveryIDHeaderwith body-hash fallback.kelos-webhook-servervia--source=generic; CRD/controller/handler and tests added; Helm addswebhookServer.sources.genericwithenvFrommulti-secret, dedicated Deployment/Service, and/webhook/ingress/gateway routes.Migration
webhookServer.sources.generic.enabled=trueand providesecretNamecontaining<SOURCE>_WEBHOOK_SECRETkeys; if none, unsigned POSTs are accepted.spec.when.webhook(source,fieldMappingwith at leastid, optionalfilters,SignatureHeader/SignaturePrefix,DeliveryIDHeader)./webhook/<source>; if signing, ensure headers/prefix match your TaskSpawner config.Written for commit 187500d. Summary will update on new commits.