Skip to content

fix: add Zod schema validation to readModels disk cache - #1099

Closed
daewoongoh wants to merge 2 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix-readmodels-zod-validation
Closed

fix: add Zod schema validation to readModels disk cache#1099
daewoongoh wants to merge 2 commits into
Zoo-Code-Org:mainfrom
daewoongoh:fix-readmodels-zod-validation

Conversation

@daewoongoh

@daewoongoh daewoongoh commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #1098

Description

This PR addresses the missing schema validation vulnerability in the model disk cache identified in the recent analysis report.

  • Extracted the Zod validation logic into a shared validateModelRecord helper to deduplicate code.
  • Added modelRecordSchema.safeParse() to the readModels function to ensure disk cache reads are strictly validated before use.
  • Any tampered or corrupted JSON will now be gracefully rejected and return undefined, preventing injection of unexpected properties.
  • Refactored and added new unit tests in modelCache.spec.ts to ensure the Zod schema behaves exactly as expected for valid/invalid records.

Test Procedure

  • Unit tests: Added specific test cases to verify the modelRecordSchema accepts valid records, empty records, and strictly rejects arrays, primitives, and improperly typed fields.
  • Run command: Tests were verified locally using npx vitest run api/providers/fetchers/__tests__/modelCache.spec.ts. All 48 tests pass successfully.
  • Type Checking: Full project TypeScript checks (turbo check-types) pass completely.

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Visual Snapshot (UI changes only): Not applicable.
  • Documentation Impact: I have considered if my changes require documentation updates.
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Visual Snapshots

N/A (Backend cache logic only)

Videos (interaction / animation only)

N/A

Documentation Updates

  • No documentation updates are required.
  • Yes, documentation updates are required.

Additional Notes

N/A

Get in Touch

hehegwk_23849

Summary by CodeRabbit

  • Bug Fixes
    • Improved model cache validation to reject malformed or invalid data.
    • Added consistent validation for both asynchronous and synchronous cache reads.
    • Invalid cached data is safely ignored instead of being loaded into the application.
    • Valid cached model data is now correctly restored from disk.

…dation logic

- Extract `validateModelRecord` helper to deduplicate Zod validation logic

- Add `modelRecordSchema.safeParse()` validation to `readModels` disk cache reads

- Update tests to ensure Zod validation strictly enforces `ModelRecord` schema

This resolves a vulnerability where a corrupted or tampered disk cache JSON file could lead to the injection of unexpected properties.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The model cache now validates JSON data against the model record schema during asynchronous and synchronous reads. Invalid data returns undefined and is not stored in the memory cache. Tests cover valid records and malformed arrays.

Changes

Model Cache Validation

Layer / File(s) Summary
Shared cache record validation
src/api/providers/fetchers/modelCache.ts, src/api/providers/fetchers/__tests__/modelCache.spec.ts
readModels validates parsed JSON through validateModelRecord. Tests cover valid model records and invalid array data.
Synchronous cache integration
src/api/providers/fetchers/modelCache.ts
Synchronous cache loading uses validateModelRecord before populating the memory cache and returning data.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • Zoo-Code-Org/Zoo-Code#6: This PR also modifies modelCache.ts, but it adds DeepSeek fetching instead of cache validation.

Suggested labels: awaiting-review

Suggested reviewers: taltas

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the added Zod validation for disk-cache reads.
Description check ✅ Passed The description includes the linked issue, implementation details, test procedure, checklist, and documentation assessment.
Linked Issues check ✅ Passed The changes satisfy issue #1098 by validating parsed disk-cache data before caching or returning it and by adding coverage for invalid records.
Out of Scope Changes check ✅ Passed The implementation and tests are focused on schema validation for the model disk cache and contain no unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/providers/fetchers/modelCache.ts 75.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/api/providers/fetchers/__tests__/modelCache.spec.ts (1)

360-385: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Test the production cache-loading path.

This block validates a duplicate local schema. It does not exercise validateModelRecord or the asynchronous and synchronous cache readers. The tests can pass if production code returns raw disk data or rejects malformed JSON incorrectly.

Add tests through the existing public cache-loading entry points. Assert that valid data is returned and cached, invalid data returns undefined, malformed JSON does not reject the read, and invalid data does not enter memoryCache. Also define the expected handling for unexpected properties and __proto__ keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/fetchers/__tests__/modelCache.spec.ts` around lines 360 -
385, Replace the duplicate local schema tests in “validateModelRecord schema
validation” with coverage through the public synchronous and asynchronous
cache-loading entry points, verifying valid records are returned and cached,
invalid records return undefined without entering memoryCache, and malformed
JSON reads resolve without rejection. Add explicit assertions for unexpected
properties and “__proto__” keys using the production schema behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/fetchers/modelCache.ts`:
- Around line 220-228: Update readModels to catch JSON.parse or file-read syntax
failures for malformed or truncated cache data and return undefined instead of
rejecting before validateModelRecord runs. Match the existing synchronous
cache-read behavior, while preserving validation for successfully parsed data.

---

Nitpick comments:
In `@src/api/providers/fetchers/__tests__/modelCache.spec.ts`:
- Around line 360-385: Replace the duplicate local schema tests in
“validateModelRecord schema validation” with coverage through the public
synchronous and asynchronous cache-loading entry points, verifying valid records
are returned and cached, invalid records return undefined without entering
memoryCache, and malformed JSON reads resolve without rejection. Add explicit
assertions for unexpected properties and “__proto__” keys using the production
schema behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64a5cfe1-be7e-429e-8fa8-f3bec2a55c24

📥 Commits

Reviewing files that changed from the base of the PR and between 992585f and d561cdc.

📒 Files selected for processing (2)
  • src/api/providers/fetchers/__tests__/modelCache.spec.ts
  • src/api/providers/fetchers/modelCache.ts

Comment on lines 220 to +228
async function readModels(cacheKey: string): Promise<ModelRecord | undefined> {
const filename = `${cacheKeyToFilename(cacheKey)}_models.json`
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
const filePath = path.join(cacheDir, filename)
const exists = await fileExistsAtPath(filePath)
return exists ? JSON.parse(await fs.readFile(filePath, "utf8")) : undefined
if (!exists) return undefined

const data = JSON.parse(await fs.readFile(filePath, "utf8"))
return validateModelRecord(data, cacheKey)

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle syntax errors in asynchronous cache reads.

JSON.parse at Line 227 throws for malformed or truncated _models.json files. The exception rejects readModels before validation runs. The synchronous path already catches this failure. Return undefined from both paths for corrupted disk data, as required by the PR objective.

Suggested error boundary
 async function readModels(cacheKey: string): Promise<ModelRecord | undefined> {
+	try {
 		const filename = `${cacheKeyToFilename(cacheKey)}_models.json`
 		const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
 		const filePath = path.join(cacheDir, filename)
 		const exists = await fileExistsAtPath(filePath)
 		if (!exists) return undefined

 		const data = JSON.parse(await fs.readFile(filePath, "utf8"))
 		return validateModelRecord(data, cacheKey)
+	} catch (error) {
+		console.error(`[MODEL_CACHE] Error loading ${cacheKey} models from disk:`, error)
+		return undefined
+	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function readModels(cacheKey: string): Promise<ModelRecord | undefined> {
const filename = `${cacheKeyToFilename(cacheKey)}_models.json`
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
const filePath = path.join(cacheDir, filename)
const exists = await fileExistsAtPath(filePath)
return exists ? JSON.parse(await fs.readFile(filePath, "utf8")) : undefined
if (!exists) return undefined
const data = JSON.parse(await fs.readFile(filePath, "utf8"))
return validateModelRecord(data, cacheKey)
async function readModels(cacheKey: string): Promise<ModelRecord | undefined> {
try {
const filename = `${cacheKeyToFilename(cacheKey)}_models.json`
const cacheDir = await getCacheDirectoryPath(ContextProxy.instance.globalStorageUri.fsPath)
const filePath = path.join(cacheDir, filename)
const exists = await fileExistsAtPath(filePath)
if (!exists) return undefined
const data = JSON.parse(await fs.readFile(filePath, "utf8"))
return validateModelRecord(data, cacheKey)
} catch (error) {
console.error(`[MODEL_CACHE] Error loading ${cacheKey} models from disk:`, error)
return undefined
}
}
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 226-226: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFile(filePath, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/fetchers/modelCache.ts` around lines 220 - 228, Update
readModels to catch JSON.parse or file-read syntax failures for malformed or
truncated cache data and return undefined instead of rejecting before
validateModelRecord runs. Match the existing synchronous cache-read behavior,
while preserving validation for successfully parsed data.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
src/api/providers/fetchers/__tests__/modelCache.spec.ts (1)

387-401: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert that invalid disk data is not cached.

The test verifies the return value but does not verify the memory-cache boundary. Assert that mockCache.set was not called after validation fails.

Proposed test update
 		expect(result).toBeUndefined()
+		expect(mockCache.set).not.toHaveBeenCalled()
 		expect(consoleErrorSpy).toHaveBeenCalledWith(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/fetchers/__tests__/modelCache.spec.ts` around lines 387 -
401, Update the invalid-schema test for getModelsFromCache to assert that
mockCache.set was not called after validation fails, while preserving the
existing undefined result and error-log assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/api/providers/fetchers/__tests__/modelCache.spec.ts`:
- Around line 387-401: Update the invalid-schema test for getModelsFromCache to
assert that mockCache.set was not called after validation fails, while
preserving the existing undefined result and error-log assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c935076f-4839-4fc5-a6af-efe61b46cbbf

📥 Commits

Reviewing files that changed from the base of the PR and between d561cdc and 36b1355.

📒 Files selected for processing (1)
  • src/api/providers/fetchers/__tests__/modelCache.spec.ts

@daewoongoh daewoongoh closed this Aug 2, 2026
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.

[BUG] Missing Schema Validation for Disk Cache JSON in readModels

1 participant