diff --git a/src/core/structured-output-schema.ts b/src/core/structured-output-schema.ts index 8f075d8d2..846fb3192 100644 --- a/src/core/structured-output-schema.ts +++ b/src/core/structured-output-schema.ts @@ -73,7 +73,22 @@ function readJsonObject(filePath: string, label: string): JsonObject { } function schemaPathFor(ref: StructuredOutputSchemaRef): string { - return path.join(getStructuredOutputSchemasDir(), ref.schema, `${ref.version}.schema.json`); + const schemasDir = getStructuredOutputSchemasDir(); + const schemaPath = path.join(schemasDir, ref.schema, `${ref.version}.schema.json`); + const resolvedPath = path.resolve(schemaPath); + const resolvedSchemasDir = path.resolve(schemasDir); + + // Prevent path traversal attacks by ensuring the resolved path is within the schemas directory + if ( + !resolvedPath.startsWith(resolvedSchemasDir + path.sep) && + resolvedPath !== resolvedSchemasDir + ) { + throw new Error( + `Invalid schema path: attempted path traversal detected for ${ref.schema}@${ref.version}`, + ); + } + + return schemaPath; } function collectAndRewriteCommonRefs(value: unknown, pendingDefs: Set): unknown { @@ -192,10 +207,10 @@ export function getMcpOutputSchema(ref: StructuredOutputSchemaRef): JsonObject { } const rootSchema = readJsonObject(schemaPathFor(ref), `${ref.schema}@${ref.version}`); - const commonSchema = readJsonObject( - path.join(getStructuredOutputSchemasDir(), '_defs', 'common.schema.json'), - 'common structured output definitions', - ); + + const schemasDir = getStructuredOutputSchemasDir(); + const commonSchemaPath = path.join(schemasDir, '_defs', 'common.schema.json'); + const commonSchema = readJsonObject(commonSchemaPath, 'common structured output definitions'); const bundled = bundleSchema(rootSchema, commonSchema); bundledSchemaCache.set(cacheKey, bundled); return cloneJson(bundled);