Skip to content

fix(api): defensive boundary hardening and zod schema guards (#342, #343, #212) - #2064

Merged
BigSimmo merged 14 commits into
mainfrom
gemini/api-defense-zod-hardening
Aug 18, 2026
Merged

fix(api): defensive boundary hardening and zod schema guards (#342, #343, #212)#2064
BigSimmo merged 14 commits into
mainfrom
gemini/api-defense-zod-hardening

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Aug 17, 2026

Copy link
Copy Markdown
Owner

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)

  • Defensive Query Parsing: In src/lib/validation/query.ts, wrapped URL instantiation and searchParams iteration in try/catch to throw structured validationError instead of allowing malformed query strings or encoding errors to escalate into unhandled 500 exceptions.
  • Route Error Handlers:
    • In src/app/api/search/route.ts, added explicit catching for SyntaxError and URIError to return structured 400 Bad Request (code: "invalid_request"). Added exported GET handler returning 405 Method Not Allowed (code: "method_not_allowed").
    • In src/app/api/search/universal/route.ts, added maximum length bounds (max(500)) on domains, caught z.ZodError, PublicApiError, SyntaxError, URIError, and TypeError returning structured 400 JSON, and added exported POST handler returning 405 Method Not Allowed.
  • Test Suite: Added tests/api-search.test.ts testing route error responses for malformed bodies, oversized queries/domains, invalid parameters, and 405 method rejection.

📜 2. Structural Object Constraint on source_metadata (#343)

  • Zod Schema: In src/lib/rag/rag-row-contracts.ts, added sourceMetadataSchema enforcing structural JSON object validation (z.record(z.string(), z.unknown(), { message: "source_metadata must be a JSON object" }).nullable()).
  • Contract Tests: In 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)

  • Universal Search Stream: In src/lib/universal-search-stream.ts, replaced unsafe type assertions on stream JSON with universalSearchStreamEventSchema.safeParse (Zod discriminated union).
  • Client Error Payloads: In src/lib/api-client-error.ts, replaced raw JSON.parse with apiErrorPayloadSchema.safeParse.
  • LocalStorage State: In src/components/clinical-dashboard/guide-progress.ts, replaced raw cast with guideProgressSchema.safeParse.
  • Private Search Scope: In src/lib/private-search-scope.ts, replaced manual validation with storedPrivateSearchScopeSchema.safeParse.
  • Cleaned Up Casts: Removed redundant as unknown as assertions in src/lib/service-catalog-mapper.ts and src/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 errors
  • npm run lint:internal: 0 errors / 0 warnings
  • npm run format: Clean

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

RAG impact: no retrieval behaviour change — adds a zod structural schema validating source_metadata is a JSON object in src/lib/rag/rag-row-contracts.ts; retrieval/ranking ordering logic is untouched.

Risk and rollout

  • Risk: low; defensive schema validation and error-boundary hardening only — no behaviour change to retrieval, ranking, or answer generation.
  • Rollback: revert this commit; the added schemas are additive and independently removable.

@supabase

supabaseBot commented Aug 17, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@github-actions

github-actionsBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 3 failed job(s):

  • Unit coverageneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Buildneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

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.

@coderabbitai

coderabbitaiBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b68d56e-2cd2-4dd8-9875-1a453d65576b

📥 Commits

Reviewing files that changed from the base of the PR and between 10ee6c0 and 67d09bb.

📒 Files selected for processing (12)
  • src/app/api/search/route.ts
  • src/app/api/search/universal/route.ts
  • src/components/clinical-dashboard/guide-progress.ts
  • src/lib/api-client-error.ts
  • src/lib/document-detail.ts
  • src/lib/private-search-scope.ts
  • src/lib/rag/rag-row-contracts.ts
  • src/lib/service-catalog-mapper.ts
  • src/lib/universal-search-stream.ts
  • src/lib/validation/query.ts
  • tests/api-search.test.ts
  • tests/rag-retrieval-row-contract.test.ts
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gemini/api-defense-zod-hardening

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo enabled auto-merge (squash) August 17, 2026 18:17
@BigSimmo
BigSimmo requested a lite review from CopilotAugust 18, 2026 01:49

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/search and /api/search/universal.
  • Tighten runtime validation for retrieval rows (source_metadata must 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
FileDescription
tests/rag-retrieval-row-contract.test.tsAdds contract tests ensuring non-object source_metadata is rejected and null is accepted.
tests/api-search.test.tsAdds route-level tests for /api/search and /api/search/universal defensive behavior and 405 handling.
src/lib/validation/query.tsWraps URL/query parsing in try/catch and throws structured validationError on malformed input.
src/lib/universal-search-stream.tsReplaces unsafe stream event casting with runtime shape checks while consuming NDJSON.
src/lib/service-catalog-mapper.tsRemoves an unnecessary unsafe cast when building catalogPayload.
src/lib/rag/rag-row-contracts.tsIntroduces sourceMetadataSchema to require source_metadata be an object (or null).
src/lib/private-search-scope.tsRefactors private-scope restore to validate parsed storage payload shape before use.
src/lib/document-detail.tsRemoves redundant unknown cast in demo document payload typing.
src/lib/api-client-error.tsReplaces raw JSON parsing with guarded payload parsing for API error responses.
src/components/clinical-dashboard/guide-progress.tsTightens localStorage JSON parsing and validates stored guide progress structure.
src/app/api/search/universal/route.tsAdds domains length bound, expands error handling to return structured 400s, and adds 405 POST handler.
src/app/api/search/route.tsAdds structured 400 handling for SyntaxError/URIError and adds 405 GET handler.
Suppressed comments (1)

src/lib/universal-search-stream.ts:43

  • UniversalSearchItem.score is defined as a required number (see src/lib/universal-search.ts), but the new NDJSON parser treats score as optional (optionalNumber(value.score)). That allows items with a missing/undefined score to be accepted and then cast to UniversalSearchItem, 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.

Comment threadsrc/lib/universal-search-stream.ts
Comment threadsrc/lib/private-search-scope.ts Outdated
@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

@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>
auto-merge was automatically disabled August 18, 2026 01:55

Head branch was pushed to by a user without write access

CopilotAI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply t...

Implemented the requested fixes from that review thread in commit 2949b37.

  • src/lib/universal-search-stream.ts: isFiniteNumber now requires Number.isFinite(...).
  • src/lib/private-search-scope.ts: expiresAt validation now rejects non-finite numbers with Number.isFinite(...).

@BigSimmo
BigSimmo enabled auto-merge (squash) August 18, 2026 02:01
@BigSimmo
BigSimmo merged commit 143448a into mainAug 18, 2026
27 checks passed
@BigSimmo
BigSimmo deleted the gemini/api-defense-zod-hardening branch August 18, 2026 04:39
Sign up for freeto 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.

3 participants

@BigSimmo