UNPKG

@felixgeelhaar/cclint

Version:

Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.

67 lines 2.85 kB
import { readFile } from 'fs/promises'; import { ContextFile } from '../domain/ContextFile.js'; import { PathValidator } from './security/PathValidator.js'; export class FileReader { pathValidator; constructor() { // Allow Markdown context files (CLAUDE.md, agents, skills) and JSON // configuration files (.claude/settings.json, settings.local.json) so // config-targeting rules such as hook-configuration are reachable via CLI. this.pathValidator = new PathValidator([ '.md', '.MD', '.markdown', '.json', ]); } async readContextFile(filePath, basePath) { try { // Validate the path for security const safePath = this.pathValidator.validatePath(filePath, basePath); // Check if file exists and is actually a file if (!this.pathValidator.isValidFile(safePath)) { throw new Error(`Path does not exist or is not a file: ${safePath}`); } // Check if file has allowed extension if (!this.pathValidator.hasAllowedExtension(safePath)) { throw new Error(`File type not allowed. Only Markdown (.md, .MD, .markdown) and JSON (.json) files are supported`); } // Read the file content const content = await readFile(safePath, 'utf-8'); // Check for suspicious content patterns this.validateFileContent(content, safePath); return new ContextFile(safePath, content); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to read file ${filePath}: ${error.message}`); } throw new Error(`Failed to read file ${filePath}: Unknown error`); } } /** * Validate file content for potential security issues * @param content The file content to validate * @param filePath The file path for error messages */ validateFileContent(content, filePath) { // Check file size (limit to 10MB) const maxSize = 10 * 1024 * 1024; // 10MB if (content.length > maxSize) { throw new Error(`File ${filePath} exceeds maximum size of 10MB`); } // Check for null bytes if (content.includes('\x00')) { throw new Error(`File ${filePath} contains null bytes`); } // Check for excessive line length (potential ReDoS attack) const lines = content.split('\n'); const maxLineLength = 10000; for (let i = 0; i < lines.length; i++) { if (lines[i].length > maxLineLength) { throw new Error(`File ${filePath} has line ${i + 1} exceeding ${maxLineLength} characters`); } } } } //# sourceMappingURL=FileReader.js.map