most-box 0.0.1 → 0.0.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/README.md +156 -73
- package/cli.js +2 -2
- package/package.json +9 -5
- package/public/app.css +1519 -0
- package/public/app.jsx +607 -399
- package/public/bundle.css +1 -0
- package/public/bundle.js +10 -14
- package/public/error-boundary.jsx +50 -0
- package/public/index.html +2 -1
- package/public/index.jsx +16 -1
- package/server.js +280 -197
- package/src/config.js +24 -7
- package/src/core/cid.js +23 -18
- package/src/index.js +400 -272
- package/src/utils/security.js +27 -24
- package/public/bundle.js.map +0 -7
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/mask-icon.svg +0 -3
- package/public/icons/most.png +0 -0
- package/public/icons/pwa-192x192.png +0 -0
- package/public/icons/pwa-512x512.png +0 -0
package/src/utils/security.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
3
4
|
|
|
4
5
|
import { MAX_FILE_SIZE } from '../config.js'
|
|
5
6
|
|
|
@@ -8,9 +9,9 @@ const DANGEROUS_PREFIXES = /^[\s.]+|[\s.]+$/
|
|
|
8
9
|
const RESERVED_NAMES = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {string} filename -
|
|
13
|
-
* @returns {string} -
|
|
12
|
+
* 清理文件名以防止安全问题
|
|
13
|
+
* @param {string} filename - 原始文件名
|
|
14
|
+
* @returns {string} - 清理后的文件名
|
|
14
15
|
*/
|
|
15
16
|
export function sanitizeFilename(filename) {
|
|
16
17
|
if (typeof filename !== 'string') {
|
|
@@ -19,25 +20,27 @@ export function sanitizeFilename(filename) {
|
|
|
19
20
|
|
|
20
21
|
let sanitized = filename
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
+
// 将反斜杠规范化为正斜杠(S3 风格路径)
|
|
23
24
|
sanitized = sanitized.replace(/\\/g, '/')
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
+
// 移除危险字符但保留 / 以支持文件夹路径
|
|
26
27
|
sanitized = sanitized.replace(/[<>:"|?*\x00-\x1f]/g, '_')
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
+
// 移除危险前缀/后缀
|
|
29
30
|
sanitized = sanitized.replace(DANGEROUS_PREFIXES, '')
|
|
30
31
|
|
|
31
|
-
//
|
|
32
|
-
|
|
32
|
+
// 防止路径遍历
|
|
33
|
+
while (sanitized.includes('..')) {
|
|
34
|
+
sanitized = sanitized.replace(/\.\./g, '_')
|
|
35
|
+
}
|
|
33
36
|
|
|
34
|
-
//
|
|
37
|
+
// 规范多个连续斜杠
|
|
35
38
|
sanitized = sanitized.replace(/\/{2,}/g, '/')
|
|
36
39
|
|
|
37
|
-
//
|
|
40
|
+
// 移除首尾斜杠
|
|
38
41
|
sanitized = sanitized.replace(/^\/+|\/+$/g, '')
|
|
39
42
|
|
|
40
|
-
//
|
|
43
|
+
// 单独清理每个路径段
|
|
41
44
|
const segments = sanitized.split('/')
|
|
42
45
|
const safeSegments = segments.map(seg => {
|
|
43
46
|
let safe = seg.replace(/[<>:"|?*]/g, '_')
|
|
@@ -54,10 +57,10 @@ export function sanitizeFilename(filename) {
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @param {string} inputPath -
|
|
59
|
-
* @param {object} options -
|
|
60
|
-
* @param {string} [options.allowedBase] -
|
|
60
|
+
* 验证并清理文件路径以防止路径遍历攻击
|
|
61
|
+
* @param {string} inputPath - 用户输入路径
|
|
62
|
+
* @param {object} options - 验证选项
|
|
63
|
+
* @param {string} [options.allowedBase] - 路径必须在的基础目录(可选)
|
|
61
64
|
* @returns {{ cleanPath: string, error?: string }}
|
|
62
65
|
*/
|
|
63
66
|
export function validateAndSanitizePath(inputPath, options = {}) {
|
|
@@ -81,7 +84,7 @@ export function validateAndSanitizePath(inputPath, options = {}) {
|
|
|
81
84
|
if (options.allowedBase) {
|
|
82
85
|
const resolvedPath = path.resolve(cleanPath)
|
|
83
86
|
const allowedBase = path.resolve(options.allowedBase)
|
|
84
|
-
if (!resolvedPath.startsWith(allowedBase)) {
|
|
87
|
+
if (resolvedPath !== allowedBase && !resolvedPath.startsWith(allowedBase + path.sep)) {
|
|
85
88
|
return { cleanPath: '', error: 'Path must be within allowed directory' }
|
|
86
89
|
}
|
|
87
90
|
}
|
|
@@ -90,9 +93,9 @@ export function validateAndSanitizePath(inputPath, options = {}) {
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
93
|
-
*
|
|
94
|
-
* @param {string} filePath -
|
|
95
|
-
* @param {number} [maxSize] -
|
|
96
|
+
* 检查文件大小是否在限制内
|
|
97
|
+
* @param {string} filePath - 文件路径
|
|
98
|
+
* @param {number} [maxSize] - 最大允许大小(字节,默认 100GB)
|
|
96
99
|
* @returns {{ valid: boolean, size?: number, error?: string }}
|
|
97
100
|
*/
|
|
98
101
|
export async function validateFileSize(filePath, maxSize = MAX_FILE_SIZE) {
|
|
@@ -123,8 +126,8 @@ export async function validateFileSize(filePath, maxSize = MAX_FILE_SIZE) {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
|
-
*
|
|
127
|
-
* @param {string} dirPath -
|
|
129
|
+
* 检查目录是否可写
|
|
130
|
+
* @param {string} dirPath - 要检查的目录路径
|
|
128
131
|
* @returns {{ writable: boolean, error?: string }}
|
|
129
132
|
*/
|
|
130
133
|
export async function checkDirectoryWritable(dirPath) {
|
|
@@ -133,7 +136,7 @@ export async function checkDirectoryWritable(dirPath) {
|
|
|
133
136
|
fs.mkdirSync(dirPath, { recursive: true })
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
const testFile = path.join(dirPath,
|
|
139
|
+
const testFile = path.join(dirPath, `.write-test-${crypto.randomUUID()}`)
|
|
137
140
|
await fs.promises.writeFile(testFile, 'test')
|
|
138
141
|
await fs.promises.unlink(testFile)
|
|
139
142
|
|
|
@@ -147,8 +150,8 @@ export async function checkDirectoryWritable(dirPath) {
|
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
/**
|
|
150
|
-
*
|
|
151
|
-
* @param {number} bytes -
|
|
153
|
+
* 获取人类可读的文件大小字符串
|
|
154
|
+
* @param {number} bytes - 字节大小
|
|
152
155
|
* @returns {string}
|
|
153
156
|
*/
|
|
154
157
|
export function formatFileSize(bytes) {
|