Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 22 additions & 111 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"test:coverage": "node node_modules/vitest/vitest.mjs run --passWithNoTests --coverage"
},
"dependencies": {
"@dagrejs/dagre": "^3.0.0",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@fontsource/inter": "^5.2.8",
Expand All @@ -61,8 +62,6 @@
"@xyflow/react": "^12.10.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dagre": "^0.8.5",
"electron-updater": "^6.8.3",
"framer-motion": "^12.38.0",
"lucide-react": "^1.6.0",
"tailwind-merge": "^3.5.0",
Expand All @@ -77,7 +76,6 @@
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/dagre": "^0.7.54",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
Expand Down
13 changes: 12 additions & 1 deletion src/main/ipc/file.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { IpcMain } from "electron";
import { readFile, writeFile } from "fs/promises";
import { readFile, writeFile, stat } from "fs/promises";
import { existsSync } from "fs";
import { extname } from "path";

// SEC-10: Maximum file size for .mzinteraction files (50 MB)
const MAX_INTERACTION_FILE_SIZE = 50 * 1024 * 1024;

/** Validate that a file path is safe for interaction file operations */
function isAllowedFilePath(filePath: string): boolean {
const ext = extname(filePath).toLowerCase();
Expand Down Expand Up @@ -50,6 +53,14 @@ export function setupFileHandlers(ipcMain: IpcMain): void {
if (!existsSync(filePath)) {
return { success: false, error: "File not found" };
}
// SEC-10: Check file size before reading to prevent memory exhaustion
const fileStats = await stat(filePath);
if (fileStats.size > MAX_INTERACTION_FILE_SIZE) {
return {
success: false,
error: `File too large (${Math.round(fileStats.size / 1024 / 1024)}MB, max ${MAX_INTERACTION_FILE_SIZE / 1024 / 1024}MB)`,
};
}
const content = await readFile(filePath, "utf-8");
return { success: true, content };
} catch (error) {
Expand Down
47 changes: 41 additions & 6 deletions src/main/ipc/project.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { IpcMain } from "electron";
import { readFile, writeFile, readdir } from "fs/promises";
import { readFile, writeFile, readdir, stat } from "fs/promises";
import { existsSync } from "fs";
import { join } from "path";

// SEC-10: Maximum file size for RPG Maker project JSON files (100 MB)
const MAX_PROJECT_FILE_SIZE = 100 * 1024 * 1024;

/** Read a project JSON file with size limit enforcement */
async function readProjectFile(filePath: string): Promise<string> {
const fileStats = await stat(filePath);
if (fileStats.size > MAX_PROJECT_FILE_SIZE) {
throw new Error(
`File too large (${Math.round(fileStats.size / 1024 / 1024)}MB, max ${MAX_PROJECT_FILE_SIZE / 1024 / 1024}MB)`,
);
}
return readFile(filePath, "utf-8");
}

interface MZMapInfo {
id: number;
name: string;
Expand Down Expand Up @@ -120,7 +134,7 @@ export function setupProjectHandlers(ipcMain: IpcMain): void {
}

try {
const data = await readFile(mapInfoFile, "utf-8");
const data = await readProjectFile(mapInfoFile);
let mapInfos: (MZMapInfo | null)[];
try {
mapInfos = JSON.parse(data) as (MZMapInfo | null)[];
Expand Down Expand Up @@ -157,7 +171,7 @@ export function setupProjectHandlers(ipcMain: IpcMain): void {
}

try {
const data = await readFile(mapFile, "utf-8");
const data = await readProjectFile(mapFile);
let mapData: RawMZMapData;
try {
mapData = JSON.parse(data) as RawMZMapData;
Expand Down Expand Up @@ -189,7 +203,7 @@ export function setupProjectHandlers(ipcMain: IpcMain): void {
}

try {
const data = await readFile(file, "utf-8");
const data = await readProjectFile(file);
let system: RawMZSystemData;
try {
system = JSON.parse(data) as RawMZSystemData;
Expand Down Expand Up @@ -219,7 +233,7 @@ export function setupProjectHandlers(ipcMain: IpcMain): void {
}

try {
const data = await readFile(file, "utf-8");
const data = await readProjectFile(file);
let system: RawMZSystemData;
try {
system = JSON.parse(data) as RawMZSystemData;
Expand Down Expand Up @@ -282,7 +296,28 @@ export function setupProjectHandlers(ipcMain: IpcMain): void {
}

try {
const data = await readFile(mapFile, "utf-8");
// SEC-8: Validate commands array structure before writing to map
if (!Array.isArray(options.commands)) {
return { success: false, error: "Commands must be an array" };
}
for (const cmd of options.commands) {
const rec = cmd as Record<string, unknown>;
if (
typeof cmd !== "object" ||
cmd === null ||
typeof rec["code"] !== "number" ||
typeof rec["indent"] !== "number" ||
!Array.isArray(rec["parameters"])
) {
return {
success: false,
error:
"Invalid command structure: each command must have numeric code, numeric indent, and parameters array",
};
}
}

const data = await readProjectFile(mapFile);
let mapData: RawMZMapData;
try {
mapData = JSON.parse(data) as RawMZMapData;
Expand Down
Loading
Loading