@tinacms/graphql 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3026,7 +3026,7 @@ var validateField = async (field) => {
3026
3026
  var package_default = {
3027
3027
  name: "@tinacms/graphql",
3028
3028
  type: "module",
3029
- version: "2.1.0",
3029
+ version: "2.1.2",
3030
3030
  main: "dist/index.js",
3031
3031
  module: "./dist/index.js",
3032
3032
  files: [
@@ -5100,8 +5100,10 @@ var Resolver = class {
5100
5100
  relativePath,
5101
5101
  templateName
5102
5102
  }) => {
5103
- const collection = this.getCollectionWithName(collectionName);
5104
- const realPath = path3.join(collection.path, relativePath);
5103
+ const { collection, realPath } = this.getValidatedPath(
5104
+ collectionName,
5105
+ relativePath
5106
+ );
5105
5107
  const alreadyExists = await this.database.documentExists(realPath);
5106
5108
  if (alreadyExists) {
5107
5109
  throw new Error(`Unable to add document, ${realPath} already exists`);
@@ -5178,6 +5180,58 @@ var Resolver = class {
5178
5180
  }
5179
5181
  return this.tinaSchema.getCollection(collectionName);
5180
5182
  };
5183
+ /**
5184
+ * validatePath ensures that the provided path remains within the boundaries
5185
+ * of the collection's directory and that the file extension matches the
5186
+ * collection's configured format. This is a critical security check to prevent
5187
+ * path traversal attacks where a user might attempt to read or write files
5188
+ * outside of the intended collection.
5189
+ */
5190
+ validatePath = (fullPath, collection, relativePath) => {
5191
+ const normalizedPath = path3.normalize(fullPath);
5192
+ const normalizedCollectionPath = path3.normalize(collection.path);
5193
+ const relative = path3.relative(normalizedCollectionPath, normalizedPath);
5194
+ if (relative.startsWith("..")) {
5195
+ throw new Error(`Invalid path: path escapes the collection directory`);
5196
+ }
5197
+ if (path3.isAbsolute(relative)) {
5198
+ throw new Error(`Invalid path: absolute paths are not allowed`);
5199
+ }
5200
+ if (relativePath) {
5201
+ const collectionFormat = collection.format || "md";
5202
+ const fileExtension = path3.extname(relativePath).toLowerCase().slice(1);
5203
+ if (fileExtension !== collectionFormat) {
5204
+ throw new Error(
5205
+ `Invalid file extension: expected '.${collectionFormat}' but got '.${fileExtension}'`
5206
+ );
5207
+ }
5208
+ }
5209
+ };
5210
+ /**
5211
+ * Helper method to get collection and construct validated path.
5212
+ * This encapsulates the common pattern of getting a collection, joining paths,
5213
+ * and validating the result, ensuring security checks are always performed.
5214
+ *
5215
+ * @param collectionName - Name of the collection
5216
+ * @param relativePath - Relative path within the collection
5217
+ * @param options - Optional configuration
5218
+ * @returns Object containing the collection and validated real path
5219
+ */
5220
+ getValidatedPath = (collectionName, relativePath, options) => {
5221
+ const collection = this.getCollectionWithName(collectionName);
5222
+ const pathSegments = [collection.path, relativePath];
5223
+ if (options?.extraSegments) {
5224
+ pathSegments.push(...options.extraSegments);
5225
+ }
5226
+ const realPath = path3.join(...pathSegments);
5227
+ const shouldValidateExtension = options?.validateExtension !== false;
5228
+ this.validatePath(
5229
+ realPath,
5230
+ collection,
5231
+ shouldValidateExtension ? relativePath : void 0
5232
+ );
5233
+ return { collection, realPath };
5234
+ };
5181
5235
  /*
5182
5236
  * Used for getDocument, get<Collection>Document.
5183
5237
  */
@@ -5185,8 +5239,10 @@ var Resolver = class {
5185
5239
  collectionName,
5186
5240
  relativePath
5187
5241
  }) => {
5188
- const collection = this.getCollectionWithName(collectionName);
5189
- const realPath = path3.join(collection.path, relativePath);
5242
+ const { collection, realPath } = this.getValidatedPath(
5243
+ collectionName,
5244
+ relativePath
5245
+ );
5190
5246
  return this.getDocument(realPath, {
5191
5247
  collection,
5192
5248
  checkReferences: true
@@ -5205,6 +5261,7 @@ var Resolver = class {
5205
5261
  relativePath,
5206
5262
  `.gitkeep.${collection.format || "md"}`
5207
5263
  );
5264
+ this.validatePath(realPath, collection);
5208
5265
  const alreadyExists = await this.database.documentExists(realPath);
5209
5266
  if (alreadyExists) {
5210
5267
  throw new Error(`Unable to add folder, ${realPath} already exists`);
@@ -5221,8 +5278,10 @@ var Resolver = class {
5221
5278
  relativePath,
5222
5279
  body
5223
5280
  }) => {
5224
- const collection = this.getCollectionWithName(collectionName);
5225
- const realPath = path3.join(collection.path, relativePath);
5281
+ const { collection, realPath } = this.getValidatedPath(
5282
+ collectionName,
5283
+ relativePath
5284
+ );
5226
5285
  const alreadyExists = await this.database.documentExists(realPath);
5227
5286
  if (alreadyExists) {
5228
5287
  throw new Error(`Unable to add document, ${realPath} already exists`);
@@ -5237,15 +5296,20 @@ var Resolver = class {
5237
5296
  newRelativePath,
5238
5297
  newBody
5239
5298
  }) => {
5240
- const collection = this.getCollectionWithName(collectionName);
5241
- const realPath = path3.join(collection.path, relativePath);
5299
+ const { collection, realPath } = this.getValidatedPath(
5300
+ collectionName,
5301
+ relativePath
5302
+ );
5242
5303
  const alreadyExists = await this.database.documentExists(realPath);
5243
5304
  if (!alreadyExists) {
5244
5305
  throw new Error(`Unable to update document, ${realPath} does not exist`);
5245
5306
  }
5246
5307
  const doc = await this.getDocument(realPath);
5247
5308
  if (newRelativePath) {
5248
- const newRealPath = path3.join(collection?.path, newRelativePath);
5309
+ const { realPath: newRealPath } = this.getValidatedPath(
5310
+ collectionName,
5311
+ newRelativePath
5312
+ );
5249
5313
  if (newRealPath === realPath) {
5250
5314
  return doc;
5251
5315
  }
@@ -5308,8 +5372,10 @@ var Resolver = class {
5308
5372
  collectionName,
5309
5373
  relativePath
5310
5374
  }) => {
5311
- const collection = this.getCollectionWithName(collectionName);
5312
- const realPath = path3.join(collection.path, relativePath);
5375
+ const { collection, realPath } = this.getValidatedPath(
5376
+ collectionName,
5377
+ relativePath
5378
+ );
5313
5379
  const alreadyExists = await this.database.documentExists(realPath);
5314
5380
  if (!alreadyExists) {
5315
5381
  throw new Error(`Unable to delete document, ${realPath} does not exist`);
@@ -5528,7 +5594,10 @@ var Resolver = class {
5528
5594
  params: yup3.object({ relativePath: yup3.string().required() }).required()
5529
5595
  })
5530
5596
  );
5531
- const realPath = path3.join(collection.path, args.relativePath);
5597
+ const realPath = this.getValidatedPath(
5598
+ collection.name,
5599
+ args.relativePath
5600
+ ).realPath;
5532
5601
  return this.updateResolveDocument({
5533
5602
  collection,
5534
5603
  realPath,
@@ -5548,7 +5617,10 @@ var Resolver = class {
5548
5617
  });
5549
5618
  }
5550
5619
  } else {
5551
- const realPath = path3.join(collection.path, args.relativePath);
5620
+ const realPath = this.getValidatedPath(
5621
+ collection.name,
5622
+ args.relativePath
5623
+ ).realPath;
5552
5624
  return this.getDocument(realPath, {
5553
5625
  collection,
5554
5626
  checkReferences: true
@@ -228,6 +228,25 @@ export declare class Resolver {
228
228
  */
229
229
  resolveLegacyValues: (oldDoc: any, collection: Collection<true>) => {};
230
230
  private getCollectionWithName;
231
+ /**
232
+ * validatePath ensures that the provided path remains within the boundaries
233
+ * of the collection's directory and that the file extension matches the
234
+ * collection's configured format. This is a critical security check to prevent
235
+ * path traversal attacks where a user might attempt to read or write files
236
+ * outside of the intended collection.
237
+ */
238
+ private validatePath;
239
+ /**
240
+ * Helper method to get collection and construct validated path.
241
+ * This encapsulates the common pattern of getting a collection, joining paths,
242
+ * and validating the result, ensuring security checks are always performed.
243
+ *
244
+ * @param collectionName - Name of the collection
245
+ * @param relativePath - Relative path within the collection
246
+ * @param options - Optional configuration
247
+ * @returns Object containing the collection and validated real path
248
+ */
249
+ private getValidatedPath;
231
250
  resolveRetrievedDocument: ({ collectionName, relativePath, }: {
232
251
  collectionName: string;
233
252
  relativePath: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tinacms/graphql",
3
3
  "type": "module",
4
- "version": "2.1.0",
4
+ "version": "2.1.2",
5
5
  "main": "dist/index.js",
6
6
  "module": "./dist/index.js",
7
7
  "files": [
@@ -37,14 +37,14 @@
37
37
  "isomorphic-git": "^1.29.0",
38
38
  "js-sha1": "^0.6.0",
39
39
  "js-yaml": "^3.14.1",
40
- "jsonpath-plus": "10.1.0",
40
+ "jsonpath-plus": "^10.3.0",
41
41
  "many-level": "^2.0.0",
42
42
  "micromatch": "4.0.8",
43
43
  "normalize-path": "^3.0.0",
44
44
  "readable-stream": "^4.7.0",
45
45
  "yup": "^1.6.1",
46
- "@tinacms/mdx": "2.0.4",
47
- "@tinacms/schema-tools": "2.4.0"
46
+ "@tinacms/schema-tools": "2.6.0",
47
+ "@tinacms/mdx": "2.0.6"
48
48
  },
49
49
  "publishConfig": {
50
50
  "registry": "https://registry.npmjs.org"
@@ -71,8 +71,8 @@
71
71
  "vite": "^4.5.9",
72
72
  "vitest": "^0.32.4",
73
73
  "zod": "^3.24.2",
74
- "@tinacms/schema-tools": "2.4.0",
75
- "@tinacms/scripts": "1.4.2"
74
+ "@tinacms/schema-tools": "2.6.0",
75
+ "@tinacms/scripts": "1.5.0"
76
76
  },
77
77
  "scripts": {
78
78
  "types": "pnpm tsc",