fix(api): defensive boundary hardening and zod schema guards (#342, #343, #212) - #2064
Conversation
This pull request has been ignored for the connected project Preview Branches by Supabase. |
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
CI triageCI failed on this PR. Automated classification of the 3 failed job(s):
Compared with main CI run #11693 (cancelled). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:11 minutes Limit details: You’ve used all 1 included review currently available under your plan. You completed 99 included PR reviews in the past 7 days; at that activity level, included reviews refill at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens API and client-side parsing boundaries by adding defensive error handling and runtime shape guards (including new tests) for /api/search, /api/search/universal, retrieval row contracts, and several JSON-parsing call sites.
Changes:
- Add defensive query/body parsing and structured error responses (including explicit 405 handlers) for
/api/searchand/api/search/universal. - Tighten runtime validation for retrieval rows (
source_metadatamust be a JSON object or null) and replace unsafe JSON/type assertions with guarded parsing. - Add/extend tests covering malformed inputs, size bounds, and contract enforcement.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/rag-retrieval-row-contract.test.ts | Adds contract tests ensuring non-object source_metadata is rejected and null is accepted. |
| tests/api-search.test.ts | Adds route-level tests for /api/search and /api/search/universal defensive behavior and 405 handling. |
| src/lib/validation/query.ts | Wraps URL/query parsing in try/catch and throws structured validationError on malformed input. |
| src/lib/universal-search-stream.ts | Replaces unsafe stream event casting with runtime shape checks while consuming NDJSON. |
| src/lib/service-catalog-mapper.ts | Removes an unnecessary unsafe cast when building catalogPayload. |
| src/lib/rag/rag-row-contracts.ts | Introduces sourceMetadataSchema to require source_metadata be an object (or null). |
| src/lib/private-search-scope.ts | Refactors private-scope restore to validate parsed storage payload shape before use. |
| src/lib/document-detail.ts | Removes redundant unknown cast in demo document payload typing. |
| src/lib/api-client-error.ts | Replaces raw JSON parsing with guarded payload parsing for API error responses. |
| src/components/clinical-dashboard/guide-progress.ts | Tightens localStorage JSON parsing and validates stored guide progress structure. |
| src/app/api/search/universal/route.ts | Adds domains length bound, expands error handling to return structured 400s, and adds 405 POST handler. |
| src/app/api/search/route.ts | Adds structured 400 handling for SyntaxError/URIError and adds 405 GET handler. |
Suppressed comments (1)
src/lib/universal-search-stream.ts:43
UniversalSearchItem.scoreis defined as a requirednumber(seesrc/lib/universal-search.ts), but the new NDJSON parser treatsscoreas optional (optionalNumber(value.score)). That allows items with a missing/undefined score to be accepted and then cast toUniversalSearchItem, violating the contract and risking downstream math/rendering issues.
if (typeof value.kind !== "string") return null;
if (typeof value.title !== "string") return null;
if (!optionalString(value.subtitle)) return null;
if (typeof value.href !== "string") return null;
if (!optionalNumber(value.score)) return null;
if (!optionalString(value.badge)) return null;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
BigSimmo
commented
Aug 18, 2026
@copilot Fix the code for all comments in this review thread. When a review comment includes a suggested change, apply the suggestion exactly. Do not make changes beyond what is described in the linked review thread. |
Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Head branch was pushed to by a user without write access
Implemented the requested fixes from that review thread in commit
|
Uh oh!
There was an error while loading. Please reload this page.
Summary of Changes
This PR implements Workstream 4: API Boundary Defense & Zod Schema Guards, addressing issues #342, #343, and #212 with defensive guards and runtime schemas across HTTP boundaries.
🛡️ 1. Defensive Boundary Hardening on
/api/search&/api/search/universal(#342)src/lib/validation/query.ts, wrapped URL instantiation andsearchParamsiteration intry/catchto throw structuredvalidationErrorinstead of allowing malformed query strings or encoding errors to escalate into unhandled 500 exceptions.src/app/api/search/route.ts, added explicit catching forSyntaxErrorandURIErrorto return structured 400 Bad Request (code: "invalid_request"). Added exportedGEThandler returning 405 Method Not Allowed (code: "method_not_allowed").src/app/api/search/universal/route.ts, added maximum length bounds (max(500)) ondomains, caughtz.ZodError,PublicApiError,SyntaxError,URIError, andTypeErrorreturning structured 400 JSON, and added exportedPOSThandler returning 405 Method Not Allowed.tests/api-search.test.tstesting route error responses for malformed bodies, oversized queries/domains, invalid parameters, and 405 method rejection.📜 2. Structural Object Constraint on
source_metadata(#343)src/lib/rag/rag-row-contracts.ts, addedsourceMetadataSchemaenforcing structural JSON object validation (z.record(z.string(), z.unknown(), { message: "source_metadata must be a JSON object" }).nullable()).tests/rag-retrieval-row-contract.test.ts, added parameterized test matrix verifying rejection of arrays, strings, numbers, and booleans.🔒 3. Replaced Unsafe Type Assertions with Zod Guards (#212)
src/lib/universal-search-stream.ts, replaced unsafe type assertions on stream JSON withuniversalSearchStreamEventSchema.safeParse(Zod discriminated union).src/lib/api-client-error.ts, replaced rawJSON.parsewithapiErrorPayloadSchema.safeParse.src/components/clinical-dashboard/guide-progress.ts, replaced raw cast withguideProgressSchema.safeParse.src/lib/private-search-scope.ts, replaced manual validation withstoredPrivateSearchScopeSchema.safeParse.as unknown asassertions insrc/lib/service-catalog-mapper.tsandsrc/lib/document-detail.ts.🧪 Verification
npx vitest run tests/api-search.test.ts tests/rag-retrieval-row-contract.test.ts tests/universal-search-stream.test.ts tests/search-scope.test.ts: 66/66 tests passed (100% green)npm run typecheck:internal: 0 errorsnpm run lint:internal: 0 errors / 0 warningsnpm run format: CleanClinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)RAG impact: no retrieval behaviour change — adds a zod structural schema validating
source_metadatais a JSON object insrc/lib/rag/rag-row-contracts.ts; retrieval/ranking ordering logic is untouched.Risk and rollout