Skip to content

fix(worker-utils): validate the form of accepted schema patterns - #9322

Merged
iscekic merged 4 commits into
mainfrom
drain/webhook-body-schema-patterns
Oct 30, 2026
Merged

iscekic merged 4 commits into
mainfrom
drain/webhook-body-schema-patterns

Conversation

@iscekic

@iscekic iscekic commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — ran the real ingest Worker and its Durable Object in workerd on this VM (vitest workers pool) and over HTTP through SELF.fetch. A trigger whose body schema carries a pattern outside the accepted subset is refused at create and update with the rule's message; a trigger whose schema is inside the subset validates deliveries as before, and a schema read back from storage still loads through getConfig and getConfigForResponse. The web form has no interactive test harness in apps/web, so its message is proven from the shared schema (see below), not in a browser.

Change

packages/worker-utils/src/webhook-body-validation.ts now checks a schema's pattern keyword against the documented accepted subset at write time:

  • packages/worker-utils/src/pattern-safety.ts parses a pattern with the unicode grammar the validator uses and reports the first rule of the subset it breaks; the message names that rule.
  • Ordinary patterns keep working, including ^issue\.(opened|closed)$, (\d{1,3}\.){3}\d{1,3}, ^(a{1,3}){1,3}$, (?:foo|bar)+, ^(?:cat|cow)+$, lookarounds, named groups, astral characters and the existing schema example; alternatives that begin alike stay accepted when a second character separates them.
  • The existing schema byte, depth and node limits and the short-circuit diagnostics of validateWebhookBody are unchanged.
  • parseStoredWebhookBodySchema parses a schema read from storage with the compile check only, so a trigger configured earlier still loads and keeps capturing; the capture path records when a stored schema is skipped.

apps/web/src/components/webhook-triggers/BodyValidationField.tsx validates the textarea with the same webhookBodySchema and renders issue.message, so the refusal appears in the form without a change:
properties.event.pattern: Pattern must not nest quantifiers that exceed 64 combined repetitions (produced by a real safeParse of that schema).

Verification

Check Result
pnpm --filter @kilocode/worker-utils vitest run 47 files, 1088 tests passed
pnpm --filter cloudflare-webhook-agent-ingest vitest run 11 files, 190 tests passed
pnpm --filter cloudflare-webhook-agent-ingest vitest run --config vitest.workers.config.ts 4 files, 79 tests passed (workerd, real DO)
pnpm --filter @kilocode/trpc run typecheck passed
bash scripts/typecheck-all.sh --changes-only passed (worker-utils, webhook-agent-ingest and their dependencies)
oxlint on both changed source trees, oxfmt --list-different, git diff --check clean

New coverage:

  • pattern-safety.test.ts: the accepted and refused forms of the subset.
  • webhook-body-validation.test.ts: the message and path of each refusal; a schema read back from storage keeps its content and reporting.
  • trigger-do.test.ts: create and update refuse the pattern; a schema written directly into the trigger row under the previous policy loads and captures.
  • routes.test.ts: the same stored schema delivers over the ingest HTTP route with the test's time bound.
  • api.test.ts: the internal API input schemas refuse the pattern and report the field path.

Production prerequisites

None.

Proxy answers

  1. Question: A trigger that already stored a pattern the new policy refuses: what should a delivery to it do?
    Answer: Drop the refused pattern and keep delivering
    Confidence: 0.62
  2. Question: How strict should the accepted pattern subset be for bounded nesting?
    Answer: Allow nesting up to a small repeat budget
    Confidence: 0.60

A webhook body schema's `pattern` keyword is now checked against the
documented accepted subset when a trigger is created or updated:
backreferences, quantifiers nested beyond the 64-repetition budget, and a
repeated alternation whose branches are not disjoint are refused with a
message that names the rule. The schema byte, depth and node limits are
unchanged.

`parseStoredWebhookBodySchema` reads a stored schema back with the compile
check only, so a trigger configured earlier still loads, and
`validateWebhookBody` leaves out a pattern the write path refuses while the
capture path records that the stored schema was not applied.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 30, 2026
@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The two-character literal-prefix refinement resolves the carried first-character false refusal: ^(?:cat|cow)+$, ^(?:foo|far)+$ and (?:(?:ab|ac)x)+ are accepted, while every skip still requires both branches to consume at least two characters, so no overlapping languages are admitted.

Assumptions: [INFERENCE] Matching behaviour was reasoned from the parser code; no regex, tests, or Worker were executed in this read-only session. Patterns that diverge only at the third character (e.g. (?:abc|abd)+) stay refused, which the author documents as an intentional conservative trade-off. No new inline comments were needed; the previous inline thread on first-character overlap is resolved by 032f8b2.

Files Reviewed (2 files, incremental pass)
  • packages/worker-utils/src/pattern-safety.ts - no issues
  • packages/worker-utils/src/pattern-safety.test.ts - no issues
Previous Review Summaries (3 snapshots, latest commit f9d8d0e)

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

Previous review (commit f9d8d0e)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

Commit f9d8d0e closes the nested-alternation, UTF-16 and double-parse gaps, but ambiguityIssue still compares only first characters, so valid deterministic alternations (including the newly reachable nested ones) are refused at write time.

Assumptions: [INFERENCE] Regex-language and backtracking behaviour was reasoned from the code; no regex or Worker was executed in this read-only session. The stored-schema fail-open is treated as intentional (author reply 4428551849): validateWebhookBody skips every keyword, although the PR evidence still says the schema's other keywords are enforced.

Overview

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

WARNING

File Line Issue
packages/worker-utils/src/pattern-safety.ts 443 firstSet-only overlap refuses valid deterministic patterns such as `^(?:cat
Files Reviewed (6 files in incremental pass)
  • packages/worker-utils/src/pattern-safety.ts - nested-alternation and UTF-16 fixes verified; 1 carried issue re-verified
  • packages/worker-utils/src/pattern-safety.test.ts - no issues
  • packages/worker-utils/src/webhook-body-validation.ts - no new issues; whole-schema skip matches the author's stated intent
  • packages/worker-utils/src/webhook-body-validation.test.ts - no issues
  • services/webhook-agent-ingest/src/dos/TriggerDO.ts - double-parse issue resolved
  • services/webhook-agent-ingest/test/integration/trigger-do.test.ts - no issues

Previous review (commit 41ecc5b)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

Incremental commit 41ecc5b is a lint-only change (string-index access for schema keys) with no behavioral impact; the material gap remains that a repeated alternation nested inside a sequence still bypasses the ambiguity check, so (?:(a|a)b)+ keeps the catastrophic backtracking this PR aims to remove.

Assumptions: [INFERENCE] Backtracking cost for accepted ambiguous patterns is stated from standard V8 semantics; no regex was executed in this read-only session. Legacy-schema behaviour under not/oneOf was derived from @cfworker/json-schema empty-schema semantics, not executed.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/worker-utils/src/pattern-safety.ts 428 Repeated alternations nested inside a sequence escape ambiguityIssue, so `(?:(a
packages/worker-utils/src/pattern-safety.ts 421 firstSet-only overlap refuses valid, deterministic patterns such as `^(?:cat
packages/worker-utils/src/webhook-body-validation.ts 315 Stripping an unsupported pattern under not/oneOf changes semantics, so legacy triggers can reject every delivery instead of continuing to capture (still present)

SUGGESTION

File Line Issue
packages/worker-utils/src/pattern-safety.ts 318 Parser walks UTF-16 code units, so [😀-😏] is falsely rejected and `(\u{1F600}
services/webhook-agent-ingest/src/dos/TriggerDO.ts 629 Each delivery parses every schema pattern twice (unsupportedPatternPaths then withoutUnsupportedPatterns) (still present)
Files Reviewed (3 files in incremental pass)
  • packages/worker-utils/src/webhook-body-validation.ts - lint-only changes, 1 carried issue on changed line
  • packages/worker-utils/src/pattern-safety.ts - unchanged in this commit, 3 carried issues re-verified
  • services/webhook-agent-ingest/src/dos/TriggerDO.ts - unchanged in this commit, 1 carried issue re-verified

Previous review (commit 147bac8)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

The pattern-safety gate has one material hole: ambiguity is only checked when the alternation is the repeat's direct body, so (?:(a|a)b)+ is still accepted and keeps the ReDoS this PR aims to remove.

Assumptions: [INFERENCE] Backtracking cost for accepted ambiguous patterns is stated from standard V8 semantics; no regex was executed in this read-only session. Legacy-schema behaviour under not/oneOf was derived from @cfworker/json-schema empty-schema semantics, not executed.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/worker-utils/src/pattern-safety.ts 428 Repeated alternations nested inside a sequence escape ambiguityIssue, so `(?:(a
packages/worker-utils/src/pattern-safety.ts 421 firstSet-only overlap refuses valid, deterministic patterns such as `^(?:cat
packages/worker-utils/src/webhook-body-validation.ts 312 Stripping an unsupported pattern under not/oneOf changes semantics, so legacy triggers can reject every delivery instead of continuing to capture

SUGGESTION

File Line Issue
packages/worker-utils/src/pattern-safety.ts 318 Parser walks UTF-16 code units, so [😀-😏] is falsely rejected and `(\u{1F600}
services/webhook-agent-ingest/src/dos/TriggerDO.ts 629 Each delivery parses every schema pattern twice (unsupportedPatternPaths then withoutUnsupportedPatterns)
Files Reviewed (8 files)
  • packages/worker-utils/src/pattern-safety.ts - 3 issues
  • packages/worker-utils/src/pattern-safety.test.ts - no issues
  • packages/worker-utils/src/webhook-body-validation.ts - 1 issue
  • packages/worker-utils/src/webhook-body-validation.test.ts - no issues
  • services/webhook-agent-ingest/src/dos/TriggerDO.ts - 1 issue
  • services/webhook-agent-ingest/src/routes/api.test.ts - no issues
  • services/webhook-agent-ingest/test/integration/routes.test.ts - no issues
  • services/webhook-agent-ingest/test/integration/trigger-do.test.ts - no issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 56.6K · Output: 9.4K · Cached: 170.5K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 30, 2026
The trpc project typechecks packages/worker-utils with
noPropertyAccessFromIndexSignature, so the schema walker reads `pattern`
and `properties` through the index signature.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 30, 2026
- The repeated-alternation check walks alternatives at every depth of the
  repeated body, not only at its top level.
- Literals and character-class ranges are read as code points, so an
  astral character stays one atom.
- A stored schema whose pattern is outside the accepted subset is not
  applied at all instead of having single keywords removed, because
  removing a keyword changes what a `not`, `anyOf` or `oneOf` parent means.
  The validation result reports those schema locations, so the capture path
  logs them from the same walk that makes the decision.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 30, 2026
…aracter

Two alternatives that begin with the same character are still unambiguously
ordered when both must consume a second character and those second characters
differ, so the repeated-alternation rule now compares a literal prefix of up to
two characters before refusing an alternation.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 30, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 66e8913 Oct 30, 2026
81 checks passed
@iscekic
iscekic deleted the drain/webhook-body-schema-patterns branch October 30, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants