@flowfuse/nr-launcher 3.1.1-df4cf86-202609281615.0 → 3.1.1

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,3 +1,17 @@
1
+ #### 3.1.1: Release
2
+
3
+ - fix: Refuse deleting the file store root and sibling paths that share its prefix (#634)
4
+ - chore: Override moment (#633)
5
+ - chore: Override uuid@8 to 11.1.1 (#632)
6
+ - Bump brace-expansion (#629)
7
+ - Bump serialize-javascript from 7.1.1 to 7.1.2 (#631)
8
+ - Bump fastify from 5.12.1 to 5.12.5 (#630)
9
+ - Bump fast-uri (#628)
10
+ - Bump ip-address from 10.4.0 to 10.7.2 (#627)
11
+ - Bump multer from 2.3.0 to 2.4.0 (#626)
12
+ - Update versions of nr-mqtt & nr-tables (#625)
13
+ - Update File-Server reference (#624)
14
+
1
15
  #### 3.1.0: Release
2
16
 
3
17
  - Update launcher to assistant@0.20 (#622)
@@ -1,6 +1,6 @@
1
1
  const { mkdir, readdir, rm, rename, stat, writeFile } = require('fs/promises')
2
2
  const { existsSync } = require('fs')
3
- const { dirname, join, normalize } = require('path')
3
+ const { dirname, join, normalize, sep } = require('path')
4
4
  const multer = require('multer')
5
5
 
6
6
  const filesInterface = (app, settings) => {
@@ -9,11 +9,18 @@ const filesInterface = (app, settings) => {
9
9
  const storage = multer.memoryStorage()
10
10
  const upload = multer({ storage })
11
11
 
12
+ const resolvePath = (path = '') => {
13
+ const fullPath = normalize(join(BASE_PATH, path))
14
+ return fullPath.replace(/[\\/]+$/, '')
15
+ }
16
+ const isInsideBase = (fullPath) => fullPath.startsWith(BASE_PATH + sep)
17
+ const isBaseOrInside = (fullPath) => fullPath === BASE_PATH || isInsideBase(fullPath)
18
+
12
19
  const regex = /^\/flowforge\/files\/_\/(.*)$/
13
20
 
14
21
  const listDirectory = async (path = '') => {
15
- const fullPath = normalize(join(BASE_PATH, path))
16
- if (fullPath.startsWith(BASE_PATH)) {
22
+ const fullPath = resolvePath(path)
23
+ if (isBaseOrInside(fullPath)) {
17
24
  const files = await readdir(fullPath, { withFileTypes: true })
18
25
  const response = {
19
26
  meta: { },
@@ -58,9 +65,9 @@ const filesInterface = (app, settings) => {
58
65
  })
59
66
 
60
67
  app.put(regex, async (request, reply) => {
61
- const fullPath = normalize(join(BASE_PATH, request.params[0]))
62
- const newPath = normalize(join(BASE_PATH, request.body.path))
63
- if (fullPath.startsWith(BASE_PATH) && newPath.startsWith(BASE_PATH)) {
68
+ const fullPath = resolvePath(request.params[0])
69
+ const newPath = resolvePath(request.body.path)
70
+ if (isInsideBase(fullPath) && isInsideBase(newPath)) {
64
71
  if (existsSync(fullPath) && existsSync(dirname(newPath))) {
65
72
  try {
66
73
  await rename(fullPath, newPath)
@@ -76,12 +83,12 @@ const filesInterface = (app, settings) => {
76
83
  })
77
84
 
78
85
  app.post(regex, upload.single('file'), async (request, reply) => {
79
- const startPath = normalize(join(BASE_PATH, request.params[0]))
80
- if (startPath.startsWith(BASE_PATH)) {
86
+ const startPath = resolvePath(request.params[0])
87
+ if (isBaseOrInside(startPath)) {
81
88
  if (request.get('content-type') === 'application/json') {
82
89
  const newPath = request.body.path
83
- const fullPath = normalize(join(startPath, newPath))
84
- if (fullPath.startsWith(BASE_PATH)) {
90
+ const fullPath = resolvePath(join(startPath.slice(BASE_PATH.length), newPath))
91
+ if (isBaseOrInside(fullPath)) {
85
92
  try {
86
93
  await mkdir(fullPath, { recursive: true })
87
94
  reply.status(201).send()
@@ -93,7 +100,9 @@ const filesInterface = (app, settings) => {
93
100
  }
94
101
  } else if (request.get('content-type').startsWith('multipart/form-data')) {
95
102
  const targetDir = dirname(startPath)
96
- if (existsSync(targetDir)) {
103
+ if (!isInsideBase(startPath)) {
104
+ reply.status(403).send()
105
+ } else if (existsSync(targetDir)) {
97
106
  await writeFile(startPath, request.file.buffer)
98
107
  reply.status(201).send()
99
108
  } else {
@@ -108,8 +117,8 @@ const filesInterface = (app, settings) => {
108
117
  })
109
118
 
110
119
  app.delete(regex, async (request, reply) => {
111
- const fullPath = normalize(join(BASE_PATH, request.params[0]))
112
- if (fullPath !== BASE_PATH && fullPath.startsWith(BASE_PATH)) {
120
+ const fullPath = resolvePath(request.params[0])
121
+ if (isInsideBase(fullPath)) {
113
122
  if (existsSync(fullPath)) {
114
123
  try {
115
124
  await rm(fullPath, {