Skip to content

[Security] Unsafe JSON parsing allows prototype pollution #39

@alfredolopez80

Description

@alfredolopez80

🔒 Security: Unsafe JSON Parsing in fs.ts

Summary

The readJson function in src/core/fs.ts performs JSON parsing without validation against prototype pollution attacks. While this requires user action to exploit (installing malicious config), it should be fixed for defense in depth.

Risk Assessment: LOW-MEDIUM - Requires user to install malicious configuration, but follows security best practices.


🐛 Vulnerability

File: src/core/fs.ts:13

export const readJson = <T>(filePath: string): T | null => {
  try {
    return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T;  // ← VULNERABLE
  } catch {
    return null;
  }
};

Problem

  • No validation against __proto__, constructor, or prototype pollution
  • No size limits (DoS via massive JSON)
  • No schema validation

Attack Vector

A malicious .claude.json or settings.json with prototype pollution:

{
  "__proto__": {
    "env": {
      "ANTHROPIC_API_KEY": "attacker-controlled-key"
    }
  }
}

Impact

  • ✅ Could inject malicious object properties
  • ✅ Could alter application behavior
  • ✅ Could override environment variables
  • ❌ Not remotely exploitable (requires local malicious config)
  • ⚠️ Requires user to install/configure untrusted variant

✅ Recommended Fix

Option 1: Use secure-json-parse (RECOMMENDED)

import { parse } from 'secure-json-parse';

export const readJson = <T>(filePath: string): T | null => {
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    return parse(content, { protoAction: 'remove' }) as T;
  } catch {
    return null;
  }
};

Option 2: Manual sanitization

export const readJson = <T>(filePath: string): T | null => {
  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    const parsed = JSON.parse(raw);
    // Sanitize by removing prototype chain
    return JSON.parse(JSON.stringify(parsed)) as T;
  } catch {
    return null;
  }
};

Option 3: Add size limit + schema validation

import { z } from 'zod';

const MAX_JSON_SIZE = 1024 * 1024; // 1MB

export const readJson = <T>(filePath: string): T | null => {
  try {
    const stats = fs.statSync(filePath);
    if (stats.size > MAX_JSON_SIZE) {
      throw new Error('JSON file too large');
    }
    const content = fs.readFileSync(filePath, 'utf8');
    return JSON.parse(content) as T;
  } catch {
    return null;
  }
};

📊 Additional Considerations

Dependency Installation

npm install secure-json-parse

Alternative: Zero-dependency approach

If you prefer not to add a dependency, Option 2 provides basic protection without external packages.


🔍 Verification

After fix:

# Test that normal JSON still works
npm test

# Manual verification
node -e "const fs = require('fs'); console.log(JSON.parse(fs.readFileSync('package.json', 'utf8')));"

📚 References


🎯 Context

Why this matters for cc-mirror:

  • This tool creates isolated variants with custom configurations
  • Users may download/share variant configurations from untrusted sources
  • Defense in depth prevents accidental compromise
  • Zero performance impact (fix is simple and safe)

Why NOT critical:

  • No remote attack surface (CLI tool, not a network service)
  • Requires user action to exploit (installing malicious configs)
  • Development-focused tool (not exposed to untrusted input)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions