@backstage/plugin-techdocs-node 1.12.11-next.2 → 1.12.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @backstage/plugin-techdocs-node
2
2
 
3
+ ## 1.12.11
4
+
5
+ ### Patch Changes
6
+
7
+ - f715f5c: Move `TechdocsContainerRunner` from `publish` to `generate`.
8
+ - 4417dd4: Fix typo and unify TechDocs casing in doc strings
9
+ - 57da51f: Add support for mapping custom tags in the techdocs yaml parser that validates the mkdocs.yaml file
10
+ - c2b63ab: Updated dependency `supertest` to `^7.0.0`.
11
+ - 3606843: Internal fixes to match `testcontainers` update
12
+ - 33ebb28: As the `@backstage/backend-common` package is deprecated, we have updated the `techdocs-node` package to stop depending on it.
13
+ - Updated dependencies
14
+ - @backstage/backend-plugin-api@1.0.0
15
+ - @backstage/catalog-model@1.7.0
16
+ - @backstage/integration@1.15.0
17
+ - @backstage/config@1.2.0
18
+ - @backstage/errors@1.2.4
19
+ - @backstage/integration-aws-node@0.1.12
20
+ - @backstage/plugin-search-common@1.2.14
21
+ - @backstage/plugin-techdocs-common@0.1.0
22
+
3
23
  ## 1.12.11-next.2
4
24
 
5
25
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -47,7 +47,29 @@ var os__default = /*#__PURE__*/_interopDefaultCompat(os);
47
47
 
48
48
  const getContentTypeForExtension = (ext) => {
49
49
  const defaultContentType = "text/plain; charset=utf-8";
50
- if (ext.match(/htm|xml|svg/i)) {
50
+ const excludedTypes = [
51
+ "text/html",
52
+ "text/xml",
53
+ "image/svg+xml",
54
+ "text/xsl",
55
+ "application/vnd.wap.xhtml+xml",
56
+ "multipart/x-mixed-replace",
57
+ "text/rdf",
58
+ "application/mathml+xml",
59
+ "application/octet-stream",
60
+ "application/rdf+xml",
61
+ "application/xhtml+xml",
62
+ "application/xml",
63
+ "text/cache-manifest",
64
+ "text/vtt"
65
+ ];
66
+ if (ext.match(
67
+ /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i
68
+ )) {
69
+ return defaultContentType;
70
+ }
71
+ const contentType = mime__default.default.lookup(ext);
72
+ if (contentType && excludedTypes.includes(contentType)) {
51
73
  return defaultContentType;
52
74
  }
53
75
  return mime__default.default.contentType(ext) || defaultContentType;
@@ -126,6 +148,15 @@ const bulkStorageOperation = async (operation, args, { concurrencyLimit } = { co
126
148
  const limiter = createLimiter__default.default(concurrencyLimit);
127
149
  await Promise.all(args.map((arg) => limiter(operation, arg)));
128
150
  };
151
+ const isValidContentPath = (bucketRoot, contentPath) => {
152
+ const relativePath = path__default.default.posix.relative(bucketRoot, contentPath);
153
+ if (relativePath === "") {
154
+ return true;
155
+ }
156
+ const outsideBase = relativePath.startsWith("..");
157
+ const differentDrive = path__default.default.posix.isAbsolute(relativePath);
158
+ return !outsideBase && !differentDrive;
159
+ };
129
160
 
130
161
  function getGeneratorKey(entity) {
131
162
  if (!entity) {
@@ -1164,6 +1195,12 @@ class AwsS3Publish {
1164
1195
  const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
1165
1196
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1166
1197
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1198
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1199
+ this.logger.error(
1200
+ `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`
1201
+ );
1202
+ throw new Error(`Metadata Not Found`);
1203
+ }
1167
1204
  try {
1168
1205
  const resp = await this.storageClient.send(
1169
1206
  new clientS3.GetObjectCommand({
@@ -1201,6 +1238,13 @@ class AwsS3Publish {
1201
1238
  const decodedUri = decodeURI(req.path.replace(/^\//, ""));
1202
1239
  const filePathNoRoot = this.legacyPathCasing ? decodedUri : lowerCaseEntityTripletInStoragePath(decodedUri);
1203
1240
  const filePath = path__default.default.posix.join(this.bucketRootPath, filePathNoRoot);
1241
+ if (!isValidContentPath(this.bucketRootPath, filePath)) {
1242
+ this.logger.error(
1243
+ `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`
1244
+ );
1245
+ res.status(404).send("File Not Found");
1246
+ return;
1247
+ }
1204
1248
  const fileExtension = path__default.default.extname(filePath);
1205
1249
  const responseHeaders = getHeadersForFileExtension(fileExtension);
1206
1250
  try {
@@ -1231,6 +1275,12 @@ class AwsS3Publish {
1231
1275
  const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
1232
1276
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1233
1277
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1278
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1279
+ this.logger.error(
1280
+ `Invalid content path found while checking if docs have been generated: ${entityRootDir}`
1281
+ );
1282
+ return Promise.resolve(false);
1283
+ }
1234
1284
  await this.storageClient.send(
1235
1285
  new clientS3.HeadObjectCommand({
1236
1286
  Bucket: this.bucketName,
@@ -1843,6 +1893,12 @@ class GoogleGCSPublish {
1843
1893
  const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
1844
1894
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1845
1895
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1896
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1897
+ this.logger.error(
1898
+ `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`
1899
+ );
1900
+ reject(new Error(`Metadata Not Found`));
1901
+ }
1846
1902
  const fileStreamChunks = [];
1847
1903
  this.storageClient.bucket(this.bucketName).file(`${entityRootDir}/techdocs_metadata.json`).createReadStream().on("error", (err) => {
1848
1904
  this.logger.error(err.message);
@@ -1863,6 +1919,13 @@ class GoogleGCSPublish {
1863
1919
  const decodedUri = decodeURI(req.path.replace(/^\//, ""));
1864
1920
  const filePathNoRoot = this.legacyPathCasing ? decodedUri : lowerCaseEntityTripletInStoragePath(decodedUri);
1865
1921
  const filePath = path__default.default.posix.join(this.bucketRootPath, filePathNoRoot);
1922
+ if (!isValidContentPath(this.bucketRootPath, filePath)) {
1923
+ this.logger.error(
1924
+ `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`
1925
+ );
1926
+ res.status(404).send("File Not Found");
1927
+ return;
1928
+ }
1866
1929
  const fileExtension = path__default.default.extname(filePath);
1867
1930
  const responseHeaders = getHeadersForFileExtension(fileExtension);
1868
1931
  this.storageClient.bucket(this.bucketName).file(filePath).createReadStream().on("pipe", () => {
@@ -1888,6 +1951,12 @@ class GoogleGCSPublish {
1888
1951
  const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
1889
1952
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1890
1953
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1954
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1955
+ this.logger.error(
1956
+ `Invalid content path found while checking if docs have been generated: ${entityRootDir}`
1957
+ );
1958
+ resolve(false);
1959
+ }
1891
1960
  this.storageClient.bucket(this.bucketName).file(`${entityRootDir}/index.html`).exists().then((response) => {
1892
1961
  resolve(response[0]);
1893
1962
  }).catch(() => {