Merge pull request #575 from sean908/fix/metadata-leak

fix: prevent metadata credential leaks
This commit is contained in:
叁月柒
2026-06-02 17:52:23 +08:00
committed by GitHub
16 changed files with 430 additions and 108 deletions
+2 -1
View File
@@ -6,6 +6,7 @@
*/
import { getDatabase } from '../../../utils/databaseAdapter.js';
import { sanitizeFileMetadata } from '../../../utils/metadataSecurity.js';
// CORS 跨域响应头
const corsHeaders = {
@@ -124,7 +125,7 @@ export async function onRequestGet(context) {
// 构建记录对象
const record = {
id: item.name,
metadata: item.metadata,
metadata: sanitizeFileMetadata(item.metadata),
};
// 如果需要包含 value 且是分块文件,读取 value
+7 -1
View File
@@ -1,4 +1,5 @@
import { readIndex } from "../../../utils/indexManager";
import { sanitizeFileMetadata } from "../../../utils/metadataSecurity.js";
export async function onRequest(context) {
const { request } = context;
@@ -18,7 +19,12 @@ export async function onRequest(context) {
count = Math.max(1, count);
const allRecords = await readIndex(context, { count: -1, includeSubdirFiles: true });
const files = allRecords.files.filter(item => item.metadata?.UploadIP === ip);
const files = allRecords.files
.filter(item => item.metadata?.UploadIP === ip)
.map(item => ({
...item,
metadata: sanitizeFileMetadata(item.metadata)
}));
return new Response(JSON.stringify({
data: files.slice(start, start + count),
+31 -20
View File
@@ -6,6 +6,11 @@ import { DiscordAPI } from '../../../utils/storage/discordAPI.js';
import { HuggingFaceAPI } from '../../../utils/storage/huggingfaceAPI.js';
import { WebDAVAPI } from '../../../utils/storage/webdavAPI.js';
import { resolveWebDAVConfig } from '../../../utils/webdavConfig.js';
import {
resolveDiscordCredentials,
resolveHuggingFaceCredentials,
resolveS3Credentials,
} from '../../../utils/channelCredentials.js';
// CORS 跨域响应头
const corsHeaders = {
@@ -145,17 +150,17 @@ async function deleteFile(env, fileId, cdnUrl, url) {
// S3 渠道的图片,需要删除S3中对应的图片
if (img.metadata?.Channel === 'S3') {
await deleteS3File(img);
await deleteS3File(env, img);
}
// Discord 渠道的图片,需要删除 Discord 中对应的消息
if (img.metadata?.Channel === 'Discord') {
await deleteDiscordFile(img);
await deleteDiscordFile(env, img);
}
// HuggingFace 渠道的图片,需要删除 HuggingFace 中对应的文件
if (img.metadata?.Channel === 'HuggingFace') {
await deleteHuggingFaceFile(img);
await deleteHuggingFaceFile(env, img);
}
// WebDAV 渠道的图片,需要删除 WebDAV 中对应的文件
@@ -183,19 +188,21 @@ async function deleteFile(env, fileId, cdnUrl, url) {
}
// 删除 S3 渠道的图片
async function deleteS3File(img) {
async function deleteS3File(env, img) {
const db = getDatabase(env);
const s3Credentials = await resolveS3Credentials(db, env, img.metadata);
const s3Client = new S3Client({
region: img.metadata?.S3Region || "auto",
endpoint: img.metadata?.S3Endpoint,
region: s3Credentials.region || "auto",
endpoint: s3Credentials.endpoint,
credentials: {
accessKeyId: img.metadata?.S3AccessKeyId,
secretAccessKey: img.metadata?.S3SecretAccessKey
accessKeyId: s3Credentials.accessKeyId,
secretAccessKey: s3Credentials.secretAccessKey
},
forcePathStyle: img.metadata?.S3PathStyle || false // 是否启用路径风格
forcePathStyle: s3Credentials.pathStyle || false // 是否启用路径风格
});
const bucketName = img.metadata?.S3BucketName;
const key = img.metadata?.S3FileKey;
const bucketName = s3Credentials.bucketName;
const key = s3Credentials.key;
try {
await s3Client.send(new DeleteObjectCommand({
@@ -210,10 +217,12 @@ async function deleteS3File(img) {
}
// 删除 Discord 渠道的图片(删除 Discord 消息)
async function deleteDiscordFile(img) {
const botToken = img.metadata?.DiscordBotToken;
const channelId = img.metadata?.DiscordChannelId;
const messageId = img.metadata?.DiscordMessageId;
async function deleteDiscordFile(env, img) {
const db = getDatabase(env);
const discordCredentials = await resolveDiscordCredentials(db, env, img.metadata);
const botToken = discordCredentials.botToken;
const channelId = discordCredentials.channelId;
const messageId = discordCredentials.messageId;
if (!botToken || !channelId || !messageId) {
console.warn('Discord file missing required metadata for deletion');
@@ -235,11 +244,13 @@ async function deleteDiscordFile(img) {
// 删除 HuggingFace 渠道的图片
async function deleteHuggingFaceFile(img) {
const token = img.metadata?.HfToken;
const repo = img.metadata?.HfRepo;
const filePath = img.metadata?.HfFilePath;
const isPrivate = img.metadata?.HfIsPrivate || false;
async function deleteHuggingFaceFile(env, img) {
const db = getDatabase(env);
const hfCredentials = await resolveHuggingFaceCredentials(db, env, img.metadata);
const token = hfCredentials.token;
const repo = hfCredentials.repo;
const filePath = hfCredentials.filePath;
const isPrivate = hfCredentials.isPrivate || false;
if (!token || !repo || !filePath) {
console.warn('HuggingFace file missing required metadata for deletion');
+10 -5
View File
@@ -3,6 +3,7 @@ import {
getIndexInfo, getIndexStorageStats
} from '../../utils/indexManager.js';
import { getDatabase } from '../../utils/databaseAdapter.js';
import { sanitizeFileMetadata } from '../../utils/metadataSecurity.js';
// CORS 跨域响应头
const corsHeaders = {
@@ -174,10 +175,7 @@ export async function onRequest(context) {
}
// 转换文件格式
const compatibleFiles = result.files.map(file => ({
name: file.id,
metadata: file.metadata
}));
const compatibleFiles = result.files.map(serializeFileRecordForManagement);
return new Response(JSON.stringify({
files: compatibleFiles,
@@ -237,7 +235,7 @@ async function getAllFileRecords(env, dir) {
continue;
}
allRecords.push(item);
allRecords.push(serializeFileRecordForManagement(item));
}
if (!cursor) break;
@@ -281,3 +279,10 @@ async function getAllFileRecords(env, dir) {
};
}
}
export function serializeFileRecordForManagement(file) {
return {
name: file.id || file.name,
metadata: sanitizeFileMetadata(file.metadata)
};
}
+2 -1
View File
@@ -1,5 +1,6 @@
import { addFileToIndex } from '../../../utils/indexManager.js';
import { getDatabase } from '../../../utils/databaseAdapter.js';
import { sanitizeFileMetadata } from '../../../utils/metadataSecurity.js';
// CORS 跨域响应头
const corsHeaders = {
@@ -102,7 +103,7 @@ export async function onRequest(context) {
return new Response(JSON.stringify({
success: true,
metadata: updatedMetadata,
metadata: sanitizeFileMetadata(updatedMetadata),
}), {
status: 200,
headers: { 'Content-Type': 'application/json', ...corsHeaders },
+48 -12
View File
@@ -5,6 +5,13 @@ import { getDatabase } from '../../../utils/databaseAdapter.js';
import { sanitizeUploadFolder } from "../../../upload/uploadTools.js";
import { WebDAVAPI } from "../../../utils/storage/webdavAPI.js";
import { resolveWebDAVConfig } from "../../../utils/webdavConfig.js";
import {
resolveDiscordCredentials,
resolveHuggingFaceCredentials,
resolveS3Credentials,
resolveTelegramCredentials,
} from "../../../utils/channelCredentials.js";
import { stripSensitiveMetadata } from "../../../utils/metadataSecurity.js";
export async function onRequest(context) {
const { request, env, params, waitUntil } = context;
@@ -151,13 +158,16 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) {
// S3 渠道的图片,需要移动S3中对应的图片
if (img.metadata?.Channel === 'S3') {
const { success, newKey, error } = await moveS3File(img, newFileId);
const { success, newKey, endpoint, bucketName, source, error } = await moveS3File(env, img, newFileId);
if (success) {
// 更新 metadata
img.metadata.S3FileKey = newFileId;
const s3ServerDomain = img.metadata.S3Endpoint.replace(/https?:\/\//, "");
img.metadata.S3Location = `https://${img.metadata.S3BucketName}.${s3ServerDomain}/${newKey}`;
const s3ServerDomain = endpoint.replace(/https?:\/\//, "");
img.metadata.S3Location = `https://${bucketName}.${s3ServerDomain}/${newKey}`;
if (source === 'config') {
img.metadata = stripSensitiveMetadata(img.metadata);
}
} else {
// do nothing
}
@@ -188,6 +198,7 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) {
// 更新文件夹信息,根目录为空,否则为 aaa/123/ 的格式
const DirectoryPath = newFileId.split('/').slice(0, -1).join('/') === '' ? '' : newFileId.split('/').slice(0, -1).join('/') + '/';
img.metadata.Directory = DirectoryPath;
img.metadata = await stripMetadataAfterConfigResolution(db, env, img.metadata);
// 更新KV存储
await db.put(newFileId, img.value, { metadata: img.metadata });
@@ -209,20 +220,39 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) {
}
}
async function stripMetadataAfterConfigResolution(db, env, metadata) {
let credentials = null;
if (metadata?.Channel === 'TelegramNew') {
credentials = await resolveTelegramCredentials(db, env, metadata);
} else if (metadata?.Channel === 'Discord') {
credentials = await resolveDiscordCredentials(db, env, metadata);
} else if (metadata?.Channel === 'HuggingFace') {
credentials = await resolveHuggingFaceCredentials(db, env, metadata);
} else if (metadata?.Channel === 'WebDAV') {
credentials = await resolveWebDAVConfig(env, metadata);
}
return credentials?.source === 'config'
? stripSensitiveMetadata(metadata)
: metadata;
}
// 移动 S3 渠道的图片
async function moveS3File(img, newFileId) {
async function moveS3File(env, img, newFileId) {
const db = getDatabase(env);
const s3Credentials = await resolveS3Credentials(db, env, img.metadata);
const s3Client = new S3Client({
region: img.metadata?.S3Region || "auto",
endpoint: img.metadata?.S3Endpoint,
region: s3Credentials.region || "auto",
endpoint: s3Credentials.endpoint,
credentials: {
accessKeyId: img.metadata?.S3AccessKeyId,
secretAccessKey: img.metadata?.S3SecretAccessKey
accessKeyId: s3Credentials.accessKeyId,
secretAccessKey: s3Credentials.secretAccessKey
},
forcePathStyle: img.metadata?.S3PathStyle || false // 是否启用路径风格
forcePathStyle: s3Credentials.pathStyle || false // 是否启用路径风格
});
const bucketName = img.metadata?.S3BucketName;
const oldKey = img.metadata?.S3FileKey;
const bucketName = s3Credentials.bucketName;
const oldKey = s3Credentials.key;
const newKey = newFileId;
try {
@@ -240,7 +270,13 @@ async function moveS3File(img, newFileId) {
}));
// 返回新的 S3 文件信息
return { success: true, newKey };
return {
success: true,
newKey,
endpoint: s3Credentials.endpoint,
bucketName,
source: s3Credentials.source
};
} catch (error) {
console.error("S3 Move Failed:", error);
return { success: false, error: error.message };
+54 -13
View File
@@ -5,6 +5,16 @@ import { getDatabase } from '../../../utils/databaseAdapter.js';
import { sanitizeUploadFolder } from "../../../upload/uploadTools.js";
import { WebDAVAPI } from "../../../utils/storage/webdavAPI.js";
import { resolveWebDAVConfig } from "../../../utils/webdavConfig.js";
import {
resolveDiscordCredentials,
resolveHuggingFaceCredentials,
resolveS3Credentials,
resolveTelegramCredentials,
} from "../../../utils/channelCredentials.js";
import {
sanitizeFileMetadata,
stripSensitiveMetadataInPlace,
} from "../../../utils/metadataSecurity.js";
// CORS 跨域响应头
const corsHeaders = {
@@ -128,13 +138,16 @@ export async function onRequest(context) {
// S3 渠道的图片,需要移动 S3 中对应的图片
if (metadata?.Channel === 'S3') {
const { success, newKey, error } = await moveS3File(fileData, newFileId);
const { success, newKey, endpoint, bucketName, source, error } = await moveS3File(env, fileData, newFileId);
if (success) {
// 更新 metadata
metadata.S3FileKey = newFileId;
const s3ServerDomain = metadata.S3Endpoint.replace(/https?:\/\//, "");
metadata.S3Location = `https://${metadata.S3BucketName}.${s3ServerDomain}/${newKey}`;
const s3ServerDomain = endpoint.replace(/https?:\/\//, "");
metadata.S3Location = `https://${bucketName}.${s3ServerDomain}/${newKey}`;
if (source === 'config') {
stripSensitiveMetadataInPlace(metadata);
}
} else {
// do nothing
}
@@ -171,6 +184,7 @@ export async function onRequest(context) {
// 更新文件夹信息,根目录为空,否则为 aaa/123/ 的格式
const DirectoryPath = newFileId.split('/').slice(0, -1).join('/') === '' ? '' : newFileId.split('/').slice(0, -1).join('/') + '/';
metadata.Directory = DirectoryPath;
await stripMetadataInPlaceAfterConfigResolution(db, env, metadata);
// 更新 KV 存储
await db.put(newFileId, fileData.value, { metadata });
@@ -192,7 +206,7 @@ export async function onRequest(context) {
return new Response(JSON.stringify({
success: true,
newFileId,
metadata,
metadata: sanitizeFileMetadata(metadata),
}), {
status: 200,
headers: { 'Content-Type': 'application/json', ...corsHeaders },
@@ -210,20 +224,41 @@ export async function onRequest(context) {
}
}
async function stripMetadataInPlaceAfterConfigResolution(db, env, metadata) {
let credentials = null;
if (metadata?.Channel === 'TelegramNew') {
credentials = await resolveTelegramCredentials(db, env, metadata);
} else if (metadata?.Channel === 'Discord') {
credentials = await resolveDiscordCredentials(db, env, metadata);
} else if (metadata?.Channel === 'HuggingFace') {
credentials = await resolveHuggingFaceCredentials(db, env, metadata);
} else if (metadata?.Channel === 'WebDAV') {
credentials = await resolveWebDAVConfig(env, metadata);
}
if (credentials?.source !== 'config') {
return;
}
stripSensitiveMetadataInPlace(metadata);
}
// 移动 S3 渠道的图片
async function moveS3File(img, newFileId) {
async function moveS3File(env, img, newFileId) {
const db = getDatabase(env);
const s3Credentials = await resolveS3Credentials(db, env, img.metadata);
const s3Client = new S3Client({
region: img.metadata?.S3Region || "auto",
endpoint: img.metadata?.S3Endpoint,
region: s3Credentials.region || "auto",
endpoint: s3Credentials.endpoint,
credentials: {
accessKeyId: img.metadata?.S3AccessKeyId,
secretAccessKey: img.metadata?.S3SecretAccessKey
accessKeyId: s3Credentials.accessKeyId,
secretAccessKey: s3Credentials.secretAccessKey
},
forcePathStyle: img.metadata?.S3PathStyle || false
forcePathStyle: s3Credentials.pathStyle || false
});
const bucketName = img.metadata?.S3BucketName;
const oldKey = img.metadata?.S3FileKey;
const bucketName = s3Credentials.bucketName;
const oldKey = s3Credentials.key;
const newKey = newFileId;
try {
@@ -241,7 +276,13 @@ async function moveS3File(img, newFileId) {
}));
// 返回新的 S3 文件信息
return { success: true, newKey };
return {
success: true,
newKey,
endpoint: s3Credentials.endpoint,
bucketName,
source: s3Credentials.source
};
} catch (error) {
console.error("S3 Move Failed:", error);
return { success: false, error: error.message };
+45 -27
View File
@@ -11,6 +11,12 @@ import {
} from './fileTools';
import { getDatabase } from '../utils/databaseAdapter.js';
import { authenticate, AUTH_SCOPE } from '../utils/auth/authCore.js';
import {
resolveDiscordCredentials,
resolveHuggingFaceCredentials,
resolveS3Credentials,
resolveTelegramCredentials,
} from '../utils/channelCredentials.js';
export async function onRequest(context) { // Contents of context object
@@ -130,8 +136,9 @@ export async function onRequest(context) { // Contents of context object
}
// 获取TG图片真实地址(支持代理域名)
const TgBotToken = imgRecord.metadata?.TgBotToken || env.TG_BOT_TOKEN;
const TgProxyUrl = imgRecord.metadata?.TgProxyUrl || '';
const tgCredentials = await resolveTelegramCredentials(db, env, imgRecord.metadata);
const TgBotToken = tgCredentials.botToken;
const TgProxyUrl = tgCredentials.proxyUrl || '';
const tgApi = new TelegramAPI(TgBotToken, TgProxyUrl);
const filePath = await tgApi.getFilePath(TgFileID);
if (filePath === null) {
@@ -205,8 +212,10 @@ async function handleTelegramChunkedFile(context, imgRecord, encodedFileName, fi
const { env, request, url, Referer } = context;
const metadata = imgRecord.metadata;
const TgBotToken = metadata.TgBotToken || env.TG_BOT_TOKEN;
const TgProxyUrl = metadata.TgProxyUrl || '';
const db = getDatabase(env);
const tgCredentials = await resolveTelegramCredentials(db, env, metadata);
const TgBotToken = tgCredentials.botToken;
const TgProxyUrl = tgCredentials.proxyUrl || '';
// 从KV的value中读取分片信息
let chunks = [];
@@ -393,11 +402,14 @@ async function fetchTelegramChunkWithRetry(botToken, chunk, proxyUrl = '', maxRe
// 处理 Discord 渠道分片文件读取
async function handleDiscordChunkedFile(context, imgRecord, encodedFileName, fileType) {
const { request, url, Referer } = context;
const { env, request, url, Referer } = context;
const metadata = imgRecord.metadata;
const botToken = metadata.DiscordBotToken;
const proxyUrl = metadata.DiscordProxyUrl;
const db = getDatabase(env);
const discordCredentials = await resolveDiscordCredentials(db, env, metadata);
const botToken = discordCredentials.botToken;
const channelId = discordCredentials.channelId;
const proxyUrl = discordCredentials.proxyUrl;
// 从KV的value中读取分片信息
let chunks = [];
@@ -495,7 +507,7 @@ async function handleDiscordChunkedFile(context, imgRecord, encodedFileName, fil
}
// 获取分片数据(每次通过 API 获取新的附件 URL)
const chunkData = await fetchDiscordChunkWithRetry(botToken, metadata.DiscordChannelId, chunk, proxyUrl, 3);
const chunkData = await fetchDiscordChunkWithRetry(botToken, channelId, chunk, proxyUrl, 3);
if (!chunkData) {
throw new Error(`Failed to fetch Discord chunk ${chunk.index} after retries`);
}
@@ -735,20 +747,22 @@ async function handleS3File(context, metadata, encodedFileName, fileType) {
// 通过 S3 API 读取文件
async function handleS3FileViaAPI(context, metadata, encodedFileName, fileType) {
const { Referer, url, request } = context;
const { Referer, url, request, env } = context;
const db = getDatabase(env);
const s3Credentials = await resolveS3Credentials(db, env, metadata);
const s3Client = new S3Client({
region: metadata?.S3Region || "auto",
endpoint: metadata?.S3Endpoint,
region: s3Credentials.region || "auto",
endpoint: s3Credentials.endpoint,
credentials: {
accessKeyId: metadata?.S3AccessKeyId,
secretAccessKey: metadata?.S3SecretAccessKey
accessKeyId: s3Credentials.accessKeyId,
secretAccessKey: s3Credentials.secretAccessKey
},
forcePathStyle: metadata?.S3PathStyle || false
forcePathStyle: s3Credentials.pathStyle || false
});
const bucketName = metadata?.S3BucketName;
const key = metadata?.S3FileKey;
const bucketName = s3Credentials.bucketName;
const key = s3Credentials.key;
try {
// 检查Range请求头
@@ -802,11 +816,13 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) {
const { env, request, url, Referer } = context;
try {
const db = getDatabase(env);
const discordCredentials = await resolveDiscordCredentials(db, env, metadata);
// 每次读取都通过 API 获取新的附件 URL(因为 Discord 附件 URL 会在约24小时后过期)
let fileUrl = null;
if (metadata.DiscordMessageId && metadata.DiscordChannelId && metadata.DiscordBotToken) {
const discordAPI = new DiscordAPI(metadata.DiscordBotToken);
fileUrl = await discordAPI.getFileURL(metadata.DiscordChannelId, metadata.DiscordMessageId);
if (discordCredentials.messageId && discordCredentials.channelId && discordCredentials.botToken) {
const discordAPI = new DiscordAPI(discordCredentials.botToken);
fileUrl = await discordAPI.getFileURL(discordCredentials.channelId, discordCredentials.messageId);
}
if (!fileUrl) {
@@ -814,8 +830,8 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) {
}
// 如果配置了代理 URL,替换 Discord CDN 域名
if (metadata.DiscordProxyUrl) {
fileUrl = fileUrl.replace('https://cdn.discordapp.com', `https://${metadata.DiscordProxyUrl}`);
if (discordCredentials.proxyUrl) {
fileUrl = fileUrl.replace('https://cdn.discordapp.com', `https://${discordCredentials.proxyUrl}`);
}
// 处理 HEAD 请求
@@ -866,20 +882,22 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) {
// 处理 HuggingFace 文件读取
async function handleHuggingFaceFile(context, metadata, encodedFileName, fileType) {
const { request, url, Referer } = context;
const { env, request, url, Referer } = context;
try {
const hfRepo = metadata.HfRepo;
const hfFilePath = metadata.HfFilePath;
const hfToken = metadata.HfToken;
const hfIsPrivate = metadata.HfIsPrivate || false;
const db = getDatabase(env);
const hfCredentials = await resolveHuggingFaceCredentials(db, env, metadata);
const hfRepo = hfCredentials.repo;
const hfFilePath = hfCredentials.filePath;
const hfToken = hfCredentials.token;
const hfIsPrivate = hfCredentials.isPrivate || false;
if (!hfRepo || !hfFilePath) {
return new Response('Error: HuggingFace file info not found', { status: 500 });
}
// 构建文件 URL
const fileUrl = metadata.HfFileUrl || `https://huggingface.co/datasets/${hfRepo}/resolve/main/${hfFilePath}`;
const fileUrl = hfCredentials.fileUrl || `https://huggingface.co/datasets/${hfRepo}/resolve/main/${hfFilePath}`;
const fileSize = HuggingFaceAPI.getMetadataFileSize(metadata);
// 处理 HEAD 请求
-4
View File
@@ -386,8 +386,6 @@ async function mergeS3ChunksInfo(context, uploadId, completedChunks, metadata) {
}
metadata.S3Endpoint = endpoint;
metadata.S3PathStyle = pathStyle;
metadata.S3AccessKeyId = accessKeyId;
metadata.S3SecretAccessKey = secretAccessKey;
metadata.S3Region = region || "auto";
metadata.S3BucketName = bucketName;
metadata.S3FileKey = finalFileId;
@@ -464,7 +462,6 @@ async function mergeTelegramChunksInfo(context, uploadId, completedChunks, metad
metadata.Channel = "TelegramNew";
metadata.ChannelName = tgChannel.name;
metadata.TgChatId = tgChatId;
metadata.TgBotToken = tgBotToken;
metadata.TgProxyUrl = tgChannel.proxyUrl || '';
metadata.IsChunked = true;
metadata.TotalChunks = completedChunks.length;
@@ -544,7 +541,6 @@ async function mergeDiscordChunksInfo(context, uploadId, completedChunks, metada
metadata.Channel = "Discord";
metadata.ChannelName = discordChannel.name;
metadata.DiscordChannelId = channelId;
metadata.DiscordBotToken = botToken;
metadata.DiscordProxyUrl = discordChannel.proxyUrl || '';
metadata.IsChunked = true;
metadata.TotalChunks = completedChunks.length;
-1
View File
@@ -1253,7 +1253,6 @@ export async function uploadLargeFileToTelegram(context, file, fullId, metadata,
metadata.Channel = "TelegramNew";
metadata.ChannelName = tgChannel.name;
metadata.TgChatId = tgChatId;
metadata.TgBotToken = tgBotToken;
metadata.TgProxyUrl = tgChannel.proxyUrl || '';
metadata.IsChunked = true;
metadata.TotalChunks = totalChunks;
@@ -107,7 +107,6 @@ export async function onRequestPost(context) {
ListType: "None",
HfRepo: hfChannel.repo,
HfFilePath: filePath,
HfToken: hfChannel.token,
HfIsPrivate: hfChannel.isPrivate || false,
HfFileUrl: fileUrl,
TimeStamp: Date.now(),
-5
View File
@@ -401,8 +401,6 @@ async function uploadFileToS3(context, fullId, metadata, returnLink) {
}
metadata.S3Endpoint = endpoint;
metadata.S3PathStyle = pathStyle;
metadata.S3AccessKeyId = accessKeyId;
metadata.S3SecretAccessKey = secretAccessKey;
metadata.S3Region = region || "auto";
metadata.S3BucketName = bucketName;
metadata.S3FileKey = s3FileName;
@@ -556,7 +554,6 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName
metadata.TgFileId = id;
metadata.TgChatId = tgChatId;
metadata.TgBotToken = tgBotToken;
// 保存代理域名配置
if (tgProxyUrl) {
metadata.TgProxyUrl = tgProxyUrl;
@@ -675,7 +672,6 @@ async function uploadFileToDiscord(context, fullId, metadata, returnLink) {
metadata.FileSize = (fileInfo.file_size / 1024 / 1024).toFixed(2);
metadata.DiscordMessageId = fileInfo.message_id;
metadata.DiscordChannelId = discordChannel.channelId;
metadata.DiscordBotToken = discordChannel.botToken;
// 注意:不存储 DiscordAttachmentUrl,因为 Discord 附件 URL 会在约24小时后过期
// 读取时会通过 API 获取新的 URL
@@ -789,7 +785,6 @@ async function uploadFileToHuggingFace(context, fullId, metadata, returnLink) {
metadata.ChannelName = hfChannel.name || "HuggingFace_env";
metadata.HfRepo = hfChannel.repo;
metadata.HfFilePath = hfFilePath;
metadata.HfToken = hfChannel.token;
metadata.HfIsPrivate = hfChannel.isPrivate || false;
metadata.HfFileUrl = result.fileUrl;
+108
View File
@@ -0,0 +1,108 @@
import { getUploadConfig } from '../api/manage/sysConfig/upload.js';
export async function resolveS3Credentials(db, env, metadata = {}) {
const channel = await findChannel(db, env, 's3', metadata.ChannelName);
if (channel) {
return {
source: 'config',
endpoint: channel.endpoint,
region: channel.region || 'auto',
bucketName: channel.bucketName,
pathStyle: channel.pathStyle || false,
accessKeyId: channel.accessKeyId,
secretAccessKey: channel.secretAccessKey,
cdnDomain: channel.cdnDomain || '',
key: metadata.S3FileKey,
};
}
return {
source: 'metadata',
endpoint: metadata.S3Endpoint,
region: metadata.S3Region || 'auto',
bucketName: metadata.S3BucketName,
pathStyle: metadata.S3PathStyle || false,
accessKeyId: metadata.S3AccessKeyId,
secretAccessKey: metadata.S3SecretAccessKey,
cdnDomain: metadata.S3CdnDomain || '',
key: metadata.S3FileKey,
};
}
export async function resolveTelegramCredentials(db, env, metadata = {}) {
const channel = await findChannel(db, env, 'telegram', metadata.ChannelName);
if (channel) {
return {
source: 'config',
botToken: channel.botToken,
chatId: channel.chatId,
proxyUrl: channel.proxyUrl || '',
fileId: metadata.TgFileId,
};
}
return {
source: 'metadata',
botToken: metadata.TgBotToken || env.TG_BOT_TOKEN,
chatId: metadata.TgChatId || env.TG_CHAT_ID,
proxyUrl: metadata.TgProxyUrl || '',
fileId: metadata.TgFileId,
};
}
export async function resolveDiscordCredentials(db, env, metadata = {}) {
const channel = await findChannel(db, env, 'discord', metadata.ChannelName);
if (channel) {
return {
source: 'config',
botToken: channel.botToken,
channelId: channel.channelId,
proxyUrl: channel.proxyUrl || '',
messageId: metadata.DiscordMessageId,
};
}
return {
source: 'metadata',
botToken: metadata.DiscordBotToken,
channelId: metadata.DiscordChannelId,
proxyUrl: metadata.DiscordProxyUrl || '',
messageId: metadata.DiscordMessageId,
};
}
export async function resolveHuggingFaceCredentials(db, env, metadata = {}) {
const channel = await findChannel(db, env, 'huggingface', metadata.ChannelName);
if (channel) {
return {
source: 'config',
token: channel.token,
repo: channel.repo,
isPrivate: channel.isPrivate || false,
filePath: metadata.HfFilePath,
fileUrl: metadata.HfFileUrl,
};
}
return {
source: 'metadata',
token: metadata.HfToken,
repo: metadata.HfRepo,
isPrivate: metadata.HfIsPrivate || false,
filePath: metadata.HfFilePath,
fileUrl: metadata.HfFileUrl,
};
}
async function findChannel(db, env, groupName, channelName) {
if (!channelName) return null;
try {
const uploadConfig = await getUploadConfig(db, env);
const channels = uploadConfig[groupName]?.channels || [];
return channels.find((channel) => channel.name === channelName) || null;
} catch (error) {
console.error(`Failed to resolve ${groupName} channel credentials:`, error);
return null;
}
}
+59
View File
@@ -0,0 +1,59 @@
const SENSITIVE_METADATA_KEYS = [
'S3AccessKeyId',
'S3SecretAccessKey',
'TgBotToken',
'DiscordBotToken',
'HfToken',
'WebDAVUsername',
'WebDAVPassword',
'WebDAVHeaders',
];
export function sanitizeFileMetadata(metadata = {}) {
if (!metadata || typeof metadata !== 'object') {
return metadata;
}
return stripSensitiveMetadata(metadata);
}
export function stripSensitiveMetadata(metadata = {}) {
if (!metadata || typeof metadata !== 'object') {
return metadata;
}
const stripped = { ...metadata };
return stripSensitiveMetadataInPlace(stripped);
}
export function stripSensitiveMetadataInPlace(metadata = {}) {
if (!metadata || typeof metadata !== 'object') {
return metadata;
}
for (const key of SENSITIVE_METADATA_KEYS) {
delete metadata[key];
}
if (metadata.WebDAVBaseUrl) {
const safeBaseUrl = stripUrlUserinfo(metadata.WebDAVBaseUrl);
if (safeBaseUrl) {
metadata.WebDAVBaseUrl = safeBaseUrl;
} else {
delete metadata.WebDAVBaseUrl;
}
}
return metadata;
}
function stripUrlUserinfo(value) {
try {
const url = new URL(value);
url.username = '';
url.password = '';
return url.toString();
} catch {
return '';
}
}
+4 -2
View File
@@ -15,13 +15,14 @@ export async function resolveWebDAVConfig(env, metadata = {}) {
|| channels.find((item) => getWebDAVBaseUrl(item) === metadataBaseUrl);
if (channel) {
return normalizeWebDAVConfig(channel);
const config = normalizeWebDAVConfig(channel);
return config ? { ...config, source: 'config' } : null;
}
} catch (error) {
console.error('Failed to resolve WebDAV channel config:', error);
}
return normalizeWebDAVConfig({
const config = normalizeWebDAVConfig({
baseUrl: metadataBaseUrl,
username: metadata.WebDAVUsername || '',
password: metadata.WebDAVPassword || '',
@@ -29,6 +30,7 @@ export async function resolveWebDAVConfig(env, metadata = {}) {
createDirectory: metadata.WebDAVCreateDirectory !== false,
publicUrl: metadata.WebDAVPublicBaseUrl || '',
});
return config ? { ...config, source: 'metadata' } : null;
}
function normalizeWebDAVConfig(config = {}) {
+60 -15
View File
@@ -1,16 +1,16 @@
{
"name": "CloudFlare-ImgBed",
"name": "cloudflare-imgbed",
"version": "2.7.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cloudflare-imgbed",
"version": "2.7.3",
"dependencies": {
"@aws-sdk/client-s3": "3.726.1",
"@cloudflare/pages-plugin-sentry": "^1.1.3",
"@hono/node-server": "^1.13.8",
"@sentry/tracing": "^7.114.0",
"better-sqlite3": "^11.9.1",
"hono": "^4.7.4",
"miniflare": "^3.20240718.0"
},
"devDependencies": {
@@ -18,6 +18,11 @@
"mocha": "^10.6.0",
"wait-on": "^7.2.0",
"wrangler": "^4.24.0"
},
"optionalDependencies": {
"@hono/node-server": "^1.13.8",
"better-sqlite3": "^11.9.1",
"hono": "^4.7.4"
}
},
"node_modules/@aws-crypto/crc32": {
@@ -1900,6 +1905,7 @@
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz",
"integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18.14.1"
},
@@ -3431,7 +3437,8 @@
"url": "https://feross.org/support"
}
],
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/better-sqlite3": {
"version": "11.10.0",
@@ -3439,6 +3446,7 @@
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
@@ -3462,6 +3470,7 @@
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
@@ -3471,6 +3480,7 @@
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"optional": true,
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -3539,6 +3549,7 @@
}
],
"license": "MIT",
"optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -3630,7 +3641,8 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/cliui": {
"version": "8.0.1",
@@ -3776,6 +3788,7 @@
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"mimic-response": "^3.1.0"
},
@@ -3791,6 +3804,7 @@
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=4.0.0"
}
@@ -3809,6 +3823,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -3851,6 +3866,7 @@
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"optional": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -3996,6 +4012,7 @@
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"optional": true,
"engines": {
"node": ">=6"
}
@@ -4035,7 +4052,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/fill-range": {
"version": "7.1.1",
@@ -4119,7 +4137,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
@@ -4216,7 +4235,8 @@
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/glob": {
"version": "8.1.0",
@@ -4338,6 +4358,7 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.4.tgz",
"integrity": "sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=16.9.0"
}
@@ -4360,7 +4381,8 @@
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/inflight": {
"version": "1.0.6",
@@ -4378,13 +4400,15 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"devOptional": true,
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
@@ -4580,6 +4604,7 @@
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -4629,6 +4654,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -4638,7 +4664,8 @@
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/mocha": {
"version": "10.8.2",
@@ -4727,13 +4754,15 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"semver": "^7.3.5"
},
@@ -4755,6 +4784,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"devOptional": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -4835,6 +4865,7 @@
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
@@ -4874,6 +4905,7 @@
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"optional": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -4894,6 +4926,7 @@
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"optional": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -4909,6 +4942,7 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4918,6 +4952,7 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"optional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -4964,6 +4999,7 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"devOptional": true,
"funding": [
{
"type": "github",
@@ -4984,6 +5020,7 @@
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5078,7 +5115,8 @@
"url": "https://feross.org/support"
}
],
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/simple-get": {
"version": "4.0.1",
@@ -5099,6 +5137,7 @@
}
],
"license": "MIT",
"optional": true,
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
@@ -5145,6 +5184,7 @@
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"optional": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -5223,6 +5263,7 @@
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
@@ -5235,6 +5276,7 @@
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
@@ -5280,6 +5322,7 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
@@ -5313,7 +5356,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/wait-on": {
"version": "7.2.0",
@@ -5600,6 +5644,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"devOptional": true,
"license": "ISC"
},
"node_modules/ws": {