@tinacms/graphql 2.3.1 → 2.4.0
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/database/bridge/filesystem.d.ts +7 -0
- package/dist/index.js +47 -10
- package/package.json +3 -3
|
@@ -4,6 +4,11 @@ import type { Bridge } from './index';
|
|
|
4
4
|
* The basic example here is for the filesystem, one is needed
|
|
5
5
|
* for GitHub has well.
|
|
6
6
|
*
|
|
7
|
+
* When `outputPath` differs from `rootPath` (multi-repo: generator +
|
|
8
|
+
* sibling content repo), paths under `tina/__generated__/` still resolve
|
|
9
|
+
* against `rootPath` — schema/graphql/lookup artifacts live only in the
|
|
10
|
+
* generator. Everything else (content files) resolves against `outputPath`.
|
|
11
|
+
*
|
|
7
12
|
* @security All public methods validate their `filepath` / `pattern`
|
|
8
13
|
* argument via `assertWithinBase` before performing any I/O. If you add a
|
|
9
14
|
* new method that accepts a path, you MUST validate it the same way.
|
|
@@ -12,6 +17,8 @@ export declare class FilesystemBridge implements Bridge {
|
|
|
12
17
|
rootPath: string;
|
|
13
18
|
outputPath: string;
|
|
14
19
|
constructor(rootPath: string, outputPath?: string);
|
|
20
|
+
private baseFor;
|
|
21
|
+
private assertGeneratedSubtree;
|
|
15
22
|
glob(pattern: string, extension: string): Promise<string[]>;
|
|
16
23
|
delete(filepath: string): Promise<void>;
|
|
17
24
|
get(filepath: string): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -3035,7 +3035,7 @@ var validateField = async (field) => {
|
|
|
3035
3035
|
var package_default = {
|
|
3036
3036
|
name: "@tinacms/graphql",
|
|
3037
3037
|
type: "module",
|
|
3038
|
-
version: "2.
|
|
3038
|
+
version: "2.4.0",
|
|
3039
3039
|
main: "dist/index.js",
|
|
3040
3040
|
module: "./dist/index.js",
|
|
3041
3041
|
files: [
|
|
@@ -4640,17 +4640,18 @@ var resolveMediaCloudToRelative = (value, config = { useRelativeMedia: true }, s
|
|
|
4640
4640
|
return value;
|
|
4641
4641
|
}
|
|
4642
4642
|
if (hasTinaMediaConfig(schema) === true) {
|
|
4643
|
-
const assetsURL = `https://${config.assetsHost}/${config.clientId}`;
|
|
4644
4643
|
const cleanMediaRoot = cleanUpSlashes(schema.config.media.tina.mediaRoot);
|
|
4645
|
-
|
|
4644
|
+
const cloudUrl = cloudUrlPattern(config.clientId);
|
|
4645
|
+
if (typeof value === "string" && cloudUrl.test(value)) {
|
|
4646
4646
|
return `${cleanMediaRoot}${stripStagingPrefix(
|
|
4647
|
-
value.replace(
|
|
4647
|
+
value.replace(cloudUrl, "")
|
|
4648
4648
|
)}`;
|
|
4649
4649
|
}
|
|
4650
4650
|
if (Array.isArray(value)) {
|
|
4651
4651
|
return value.map((v) => {
|
|
4652
4652
|
if (!v || typeof v !== "string") return v;
|
|
4653
|
-
|
|
4653
|
+
if (!cloudUrl.test(v)) return v;
|
|
4654
|
+
const strippedURL = v.replace(cloudUrl, "");
|
|
4654
4655
|
return `${cleanMediaRoot}${stripStagingPrefix(strippedURL)}`;
|
|
4655
4656
|
});
|
|
4656
4657
|
}
|
|
@@ -4686,12 +4687,14 @@ var resolveMediaRelativeToCloud = (value, config = { useRelativeMedia: true }, s
|
|
|
4686
4687
|
return value;
|
|
4687
4688
|
}
|
|
4688
4689
|
};
|
|
4689
|
-
var stagingPrefix = (config) => config.branch && config.branch !== config.mediaBranch ? `/__staging/${
|
|
4690
|
-
var STAGING_SEGMENT = /^\/__staging
|
|
4690
|
+
var stagingPrefix = (config) => config.branch && config.branch !== config.mediaBranch ? `/__staging/${config.branch}/__file` : "";
|
|
4691
|
+
var STAGING_SEGMENT = /^\/__staging\/.+?\/__file(\/.*)$/;
|
|
4691
4692
|
var stripStagingPrefix = (path9) => {
|
|
4692
4693
|
const match = path9.match(STAGING_SEGMENT);
|
|
4693
4694
|
return match ? match[1] : path9;
|
|
4694
4695
|
};
|
|
4696
|
+
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4697
|
+
var cloudUrlPattern = (clientId) => new RegExp(`^https://[^/]+/${escapeRegExp(clientId)}`);
|
|
4695
4698
|
var cleanUpSlashes = (path9) => {
|
|
4696
4699
|
if (path9) {
|
|
4697
4700
|
return `/${path9.replace(/^\/+|\/+$/gm, "")}`;
|
|
@@ -8121,6 +8124,13 @@ function assertWithinBase(filepath, baseDir) {
|
|
|
8121
8124
|
}
|
|
8122
8125
|
return resolved;
|
|
8123
8126
|
}
|
|
8127
|
+
var GENERATED_PATH_PREFIXES = ["tina/__generated__/", ".tina/__generated__/"];
|
|
8128
|
+
function isGeneratedPath(filepath) {
|
|
8129
|
+
const normalized = filepath.replace(/\\/g, "/");
|
|
8130
|
+
return GENERATED_PATH_PREFIXES.some(
|
|
8131
|
+
(prefix) => normalized.startsWith(prefix)
|
|
8132
|
+
);
|
|
8133
|
+
}
|
|
8124
8134
|
var FilesystemBridge = class {
|
|
8125
8135
|
rootPath;
|
|
8126
8136
|
outputPath;
|
|
@@ -8128,6 +8138,30 @@ var FilesystemBridge = class {
|
|
|
8128
8138
|
this.rootPath = path7.resolve(rootPath);
|
|
8129
8139
|
this.outputPath = outputPath ? path7.resolve(outputPath) : this.rootPath;
|
|
8130
8140
|
}
|
|
8141
|
+
// Picks the base directory for a given path. The `assertWithinBase` check
|
|
8142
|
+
// in delete/get/put still runs against the chosen base, so a path like
|
|
8143
|
+
// `tina/__generated__/../../escape.txt` is still rejected — just rejected
|
|
8144
|
+
// against `rootPath` rather than `outputPath`.
|
|
8145
|
+
baseFor(filepath) {
|
|
8146
|
+
return isGeneratedPath(filepath) ? this.rootPath : this.outputPath;
|
|
8147
|
+
}
|
|
8148
|
+
// Defense-in-depth for generated-path routing: assertWithinBase already
|
|
8149
|
+
// rejects paths that escape rootPath, but a path like
|
|
8150
|
+
// `tina/__generated__/../../.env` would resolve to `<rootPath>/.env` —
|
|
8151
|
+
// technically inside rootPath but outside the generated subtree the
|
|
8152
|
+
// routing is supposed to grant access to. Verify the resolved path stays
|
|
8153
|
+
// inside the matching generated subdirectory.
|
|
8154
|
+
assertGeneratedSubtree(filepath, resolved) {
|
|
8155
|
+
if (!isGeneratedPath(filepath)) return;
|
|
8156
|
+
const normalized = filepath.replace(/\\/g, "/");
|
|
8157
|
+
const subdir = normalized.startsWith(".tina/__generated__/") ? ".tina/__generated__" : "tina/__generated__";
|
|
8158
|
+
const generatedRoot = path7.resolve(path7.join(this.rootPath, subdir));
|
|
8159
|
+
if (resolved !== generatedRoot && !resolved.startsWith(generatedRoot + path7.sep)) {
|
|
8160
|
+
throw new Error(
|
|
8161
|
+
`Path traversal detected: "${filepath}" routed via generated prefix but resolved outside ${generatedRoot}`
|
|
8162
|
+
);
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8131
8165
|
async glob(pattern, extension) {
|
|
8132
8166
|
const basePath = assertWithinBase(pattern, this.outputPath);
|
|
8133
8167
|
const items = await fg(
|
|
@@ -8143,16 +8177,19 @@ var FilesystemBridge = class {
|
|
|
8143
8177
|
);
|
|
8144
8178
|
}
|
|
8145
8179
|
async delete(filepath) {
|
|
8146
|
-
const resolved = assertWithinBase(filepath, this.
|
|
8180
|
+
const resolved = assertWithinBase(filepath, this.baseFor(filepath));
|
|
8181
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8147
8182
|
await fs2.remove(resolved);
|
|
8148
8183
|
}
|
|
8149
8184
|
async get(filepath) {
|
|
8150
|
-
const resolved = assertWithinBase(filepath, this.
|
|
8185
|
+
const resolved = assertWithinBase(filepath, this.baseFor(filepath));
|
|
8186
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8151
8187
|
return (await fs2.readFile(resolved)).toString();
|
|
8152
8188
|
}
|
|
8153
8189
|
async put(filepath, data, basePathOverride) {
|
|
8154
|
-
const basePath = basePathOverride || this.
|
|
8190
|
+
const basePath = basePathOverride || this.baseFor(filepath);
|
|
8155
8191
|
const resolved = assertWithinBase(filepath, basePath);
|
|
8192
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8156
8193
|
await fs2.outputFile(resolved, data);
|
|
8157
8194
|
}
|
|
8158
8195
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/graphql",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"files": [
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"vite": "^4.5.9",
|
|
73
73
|
"vitest": "^0.32.4",
|
|
74
74
|
"zod": "^3.24.2",
|
|
75
|
-
"@tinacms/
|
|
76
|
-
"@tinacms/
|
|
75
|
+
"@tinacms/scripts": "1.6.1",
|
|
76
|
+
"@tinacms/schema-tools": "2.7.4"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"types": "pnpm tsc",
|