Skip to content

harden /api/orchestrate: cap intent input (ReDoS) + build compose steps in-process (SSRF) - #15

Open
sanjeevkkansal wants to merge 1 commit into
mainfrom
fix-cardo-orchestrate-hardening
Open

harden /api/orchestrate: cap intent input (ReDoS) + build compose steps in-process (SSRF)#15
sanjeevkkansal wants to merge 1 commit into
mainfrom
fix-cardo-orchestrate-hardening

Conversation

@sanjeevkkansal

Copy link
Copy Markdown

What

Two security hardenings on the /api/orchestrate surface.

1. Cap natural-language intent input (ReDoS)

parseIntentHeuristic runs several regexes over the user's free text, and the keyless heuristic path is the production parse. An unbounded body could drive polynomial backtracking and stall the event loop. This caps the text before it reaches any regex:

  • checkIntentText (new lib/orchestration/intent-input.ts) normalizes the body and rejects empty → 400, over-cap (2000 chars) → 413, at the request boundary in app/api/orchestrate/route.ts.
  • parseIntent also clamps length, as defense-in-depth for any caller.

Addresses the js/polynomial-redos findings in lib/orchestration/ai-router.ts.

2. Build compose leaf steps in-process (SSRF)

build-compose-step previously derived baseHost from new URL(req.url).origin and self-fetched ${baseHost}/api/orchestrate/build. The request host is attacker-controllable, which made the self-fetch a self-SSRF vector. The leaf handlers (build, build-yield) only read their JSON body, so this calls them directly in-process — no network hop, no trust in the request host, behaviorally equivalent.

Addresses the js/request-forgery finding at app/api/orchestrate/build-compose-step.

Tests

  • New tests/intent-input.test.ts (8 cases): empty/whitespace → 400, non-string → 400, exactly-at-cap accepted, over-cap → 413, and a fast-return assertion on a pathological ~20 KB body.
  • Full suite: 219 passed (25 files). tsc --noEmit clean. eslint 0 errors.

…ps in-process (SSRF)

Cap the free-text intent at 2000 chars before it reaches parseIntent's
heuristic regexes, so an unbounded body can't drive polynomial backtracking
and stall the event loop (checkIntentText → 413; parseIntent clamps too, as
defense-in-depth for any caller). The keyless heuristic path is the production
parse, so this guards the live path.

Build compose leaf steps by calling the build / build-yield route handlers
in-process instead of self-fetching a URL whose host is derived from the
incoming request — that host is attacker-controllable and made the self-fetch
a self-SSRF vector. The leaf handlers only read their JSON body, so a direct
call is equivalent with no network hop.
@sanjeevkkansal
sanjeevkkansal requested a review from anil-rome July 22, 2026 15:58
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.

1 participant