feat: Add risk command scanning for hook uploads

This commit is contained in:
Peifan Li
2025-12-30 23:00:38 -05:00
parent 9aa949a2a5
commit c32fa3e7ca
12 changed files with 132 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import React, { useState } from 'react';
import { useLanguage } from '../../contexts/LanguageContext';
import { Settings } from '../../types';
import ConfirmationModal from '../ConfirmationModal';
import PasswordModal from '../PasswordModal';
interface HookSettingsProps {
settings: Settings;
@@ -17,6 +18,11 @@ const API_URL = import.meta.env.VITE_API_URL;
const HookSettings: React.FC<HookSettingsProps> = () => {
const { t } = useLanguage();
const [deleteHookName, setDeleteHookName] = useState<string | null>(null);
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [passwordError, setPasswordError] = useState<string | undefined>(undefined);
const [isVerifying, setIsVerifying] = useState(false);
const [pendingUpload, setPendingUpload] = useState<{ hookName: string; file: File } | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
const { data: hookStatus, refetch: refetchHooks, isLoading } = useQuery({
queryKey: ['hookStatus'],
@@ -36,6 +42,20 @@ const HookSettings: React.FC<HookSettingsProps> = () => {
},
onSuccess: () => {
refetchHooks();
setPendingUpload(null);
setUploadError(null);
},
onError: (error: any) => {
console.error('Upload failed:', error);
const message = error.response?.data?.message || error.message;
// Try to match risk command error
// Backend sends: "Risk command detected: {command}. Upload rejected."
const riskMatch = message?.match(/Risk command detected: (.*)\. Upload rejected\./);
if (riskMatch && riskMatch[1]) {
setUploadError(t('riskCommandDetected', { command: riskMatch[1] }));
} else {
setUploadError(message || t('uploadFailed'));
}
}
});
@@ -58,10 +78,35 @@ const HookSettings: React.FC<HookSettingsProps> = () => {
return;
}
uploadMutation.mutate({ hookName, file });
// Reset input so the same file can be selected again
e.target.value = '';
setPendingUpload({ hookName, file });
setPasswordError(undefined);
setUploadError(null);
setShowPasswordModal(true);
};
const handlePasswordConfirm = async (password: string) => {
setIsVerifying(true);
setPasswordError(undefined);
try {
await axios.post(`${API_URL}/settings/verify-password`, { password });
setShowPasswordModal(false);
if (pendingUpload) {
uploadMutation.mutate(pendingUpload);
}
} catch (error: any) {
console.error('Password verification failed:', error);
if (error.response?.status === 429) {
const waitTime = error.response.data.waitTime;
setPasswordError(t('tooManyAttempts') + ` Try again in ${Math.ceil(waitTime / 1000)}s`);
} else {
setPasswordError(t('incorrectPassword'));
}
} finally {
setIsVerifying(false);
}
};
const handleDelete = (hookName: string) => {
@@ -109,6 +154,12 @@ const HookSettings: React.FC<HookSettingsProps> = () => {
{t('taskHooksWarning')}
</Alert>
{uploadError && (
<Alert severity="error" sx={{ mb: 3 }} onClose={() => setUploadError(null)}>
{uploadError}
</Alert>
)}
{isLoading ? (
<CircularProgress />
) : (
@@ -185,6 +236,20 @@ const HookSettings: React.FC<HookSettingsProps> = () => {
cancelText={t('cancel') || 'Cancel'}
isDanger={true}
/>
<PasswordModal
isOpen={showPasswordModal}
onClose={() => {
setShowPasswordModal(false);
setPendingUpload(null);
setPasswordError(undefined);
}}
onConfirm={handlePasswordConfirm}
title={t('enterPassword')}
message={t('enterPasswordToUploadHook') || 'Please enter your password to upload this hook script.'}
error={passwordError}
isLoading={isVerifying}
/>
</Box>
);
};

View File

@@ -615,6 +615,8 @@ export const ar = {
taskHooks: 'خطافات المهام',
taskHooksDescription: 'نفذ أوامر shell مخصصة في نقاط محددة من دورة حياة المهمة. متغيرات البيئة المتاحة: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'تحذير: يتم تشغيل الأوامر بصلاحيات الخادم. استخدم بحذر.',
enterPasswordToUploadHook: 'الرجاء إدخال كلمة المرور لتحميل نص Hook هذا.',
riskCommandDetected: 'تم اكتشاف أمر خطر: {command}. تم رفض التحميل.',
hookTaskBeforeStart: 'قبل بدء المهمة',
hookTaskBeforeStartHelper: 'ينفذ قبل بدء التنزيل.',
hookTaskSuccess: 'نجاح المهمة',

View File

@@ -598,6 +598,8 @@ export const de = {
taskHooks: 'Aufgaben-Hoks',
taskHooksDescription: 'Führen Sie benutzerdefinierte Shell-Befehle an bestimmten Punkten im Aufgabenlebenszyklus aus. Verfügbare Umgebungsvariablen: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Warnung: Befehle werden mit den Berechtigungen des Servers ausgeführt. Mit Vorsicht verwenden.',
enterPasswordToUploadHook: 'Bitte geben Sie Ihr Passwort ein, um dieses Hook-Skript hochzuladen.',
riskCommandDetected: 'Risikobefehl erkannt: {command}. Upload abgelehnt.',
hookTaskBeforeStart: 'Vor Aufgabenstart',
hookTaskBeforeStartHelper: 'Wird ausgeführt, bevor der Download beginnt.',
hookTaskSuccess: 'Aufgabe Erfolgreich',

View File

@@ -132,6 +132,8 @@ export const en = {
deleteHook: 'Delete Hook Script',
confirmDeleteHook: 'Are you sure you want to delete this hook script?',
uploadHook: 'Upload .sh',
enterPasswordToUploadHook: 'Please enter your password to upload this hook script.',
riskCommandDetected: 'Risk command detected: {command}. Upload rejected.',
cleanupTempFilesActiveDownloads:
"Cannot clean up temporary files while downloads are active. Please wait for all downloads to complete or cancel them first.",
formatFilenamesSuccess:

View File

@@ -647,6 +647,8 @@ export const fr = {
taskHooks: 'Crochets de Tâche',
taskHooksDescription: 'Exécutez des commandes shell personnalisées à des points spécifiques du cycle de vie de la tâche. Variables d\'environnement disponibles : MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Avertissement : Les commandes s\'exécutent avec les permissions du serveur. À utiliser avec prudence.',
enterPasswordToUploadHook: 'Veuillez entrer votre mot de passe pour télécharger ce script Hook.',
riskCommandDetected: 'Commande à risque détectée : {command}. Téléchargement rejeté.',
hookTaskBeforeStart: 'Avant le Début de la Tâche',
hookTaskBeforeStartHelper: 'S\'exécute avant le début du téléchargement.',
hookTaskSuccess: 'Tâche Réussie',

View File

@@ -626,6 +626,8 @@ export const ja = {
taskHooks: 'タスクフック',
taskHooksDescription: 'タスクライフサイクルの特定のポイントでカスタムシェルコマンドを実行します。利用可能な環境変数: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH。',
taskHooksWarning: '警告: コマンドはサーバーの権限で実行されます。注意して使用してください。',
enterPasswordToUploadHook: 'このフック・スクリプトをアップロードするにはパスワードを入力してください。',
riskCommandDetected: '危険なコマンドが検出されました: {command}。アップロードは拒否されました。',
hookTaskBeforeStart: 'タスク開始前',
hookTaskBeforeStartHelper: 'ダウンロードが始まる前に実行されます。',
hookTaskSuccess: 'タスク成功',

View File

@@ -614,6 +614,8 @@ export const ko = {
taskHooks: '태스크 훅',
taskHooksDescription: '태스크 수명 주기의 특정 지점에서 사용자 지정 셸 명령을 실행합니다. 사용 가능한 환경 변수: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: '경고: 명령은 서버 권한으로 실행됩니다. 주의해서 사용하십시오.',
enterPasswordToUploadHook: '이 훅 스크립트를 업로드하려면 비밀번호를 입력하십시오.',
riskCommandDetected: '위험한 명령 감지됨: {command}. 업로드 거부됨.',
hookTaskBeforeStart: '태스크 시작 전',
hookTaskBeforeStartHelper: '다운로드가 시작되기 전에 실행됩니다.',
hookTaskSuccess: '태스크 성공',

View File

@@ -625,6 +625,8 @@ export const pt = {
taskHooks: 'Ganchos de Tarefa',
taskHooksDescription: 'Execute comandos shell personalizados em pontos específicos do ciclo de vida da tarefa. Variáveis de ambiente disponíveis: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Aviso: Os comandos são executados com as permissões do servidor. Use com cautela.',
enterPasswordToUploadHook: 'Por favor, digite sua senha para fazer upload deste script Hook.',
riskCommandDetected: 'Comando de risco detectado: {command}. Upload rejeitado.',
hookTaskBeforeStart: 'Antes do Início da Tarefa',
hookTaskBeforeStartHelper: 'Executa antes do download começar.',
hookTaskSuccess: 'Tarefa com Sucesso',

View File

@@ -620,6 +620,8 @@ export const ru = {
taskHooks: 'Хуки Задач',
taskHooksDescription: 'Выполняйте пользовательские shell-команды в определенные моменты жизненного цикла задачи. Доступные переменные окружения: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Предупреждение: Команды выполняются с правами сервера. Используйте с осторожностью.',
enterPasswordToUploadHook: 'Пожалуйста, введите пароль для загрузки этого Hook-скрипта.',
riskCommandDetected: 'Обнаружена опасная команда: {command}. Загрузка отклонена.',
hookTaskBeforeStart: 'Перед Началом Задачи',
hookTaskBeforeStartHelper: 'Выполняется перед началом загрузки.',
hookTaskSuccess: 'Успех Задачи',

View File

@@ -612,6 +612,8 @@ export const zh = {
taskHooks: '任务钩子',
taskHooksDescription: '在任务生命周期的特定时间点执行自定义 Shell 命令。可用环境变量: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH。',
taskHooksWarning: '警告:命令将以服务器权限运行。请谨慎使用。',
enterPasswordToUploadHook: '请输入密码以上传此 Hook 脚本。',
riskCommandDetected: '检测到危险命令:{command}。上传已拒绝。',
hookTaskBeforeStart: '任务开始前',
hookTaskBeforeStartHelper: '在下载开始前执行。',
hookTaskSuccess: '任务成功',