security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed): - CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed - HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted - HIGH: tar slip prevention, S3 SSRF validation, backup ID validation - MEDIUM: remember-me random secret, TOTP session rotation, password re-auth - LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation Container reliability: - Memory limits on all 37 containers (OOM prevention) - Exited vs stopped state distinction with health-aware status badges - Crash recovery coordination (no more restart cascade) - User-stopped tracking survives reboots - Tiered boot recovery (databases → core → services → apps) UI: - Wallet TransactionsModal, health-aware app status badges - Restart button on containers, exited/crashed red state - Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch - Apps sticky header removed, dev faucet, mutable mock wallet Infrastructure: - LND REST port 8080 exposed over Tor (LND Connect fix) - Nginx cookie_session fix, deploy script Tor config updated - Dev environment: podman auto-start, boot mode simulation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
28763c4f09
commit
84a56c80de
@@ -247,7 +247,7 @@
|
||||
<div data-controller-container tabindex="0" class="mb-6">
|
||||
<button
|
||||
@click="showChangePasswordModal = true"
|
||||
class="w-full flex items-center justify-center gap-2 mb-4 px-4 py-2 rounded-lg border border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10 transition-colors"
|
||||
class="w-full flex items-center justify-center gap-2 mb-4 px-4 py-2 rounded-lg glass-button glass-button-warning font-medium"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
@@ -341,7 +341,7 @@
|
||||
</div>
|
||||
<span
|
||||
class="text-xs font-semibold px-2 py-1 rounded-full"
|
||||
:class="totpEnabled ? 'bg-green-500/20 text-green-400' : 'bg-white/10 text-white/50'"
|
||||
:class="totpEnabled ? 'status-success' : 'bg-white/10 text-white/50'"
|
||||
>
|
||||
{{ totpEnabled ? t('common.enabled') : t('common.disabled') }}
|
||||
</span>
|
||||
@@ -349,7 +349,7 @@
|
||||
<button
|
||||
v-if="!totpEnabled"
|
||||
@click="showTotpSetupModal = true"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10 transition-colors"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg glass-button glass-button-warning font-medium"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
@@ -359,7 +359,7 @@
|
||||
<button
|
||||
v-else
|
||||
@click="showTotpDisableModal = true"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-red-500/50 text-red-400 font-medium hover:bg-red-500/10 transition-colors"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg glass-button glass-button-danger font-medium"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" />
|
||||
@@ -617,7 +617,7 @@
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border transition-colors"
|
||||
:class="claudeConnected
|
||||
? 'border-white/20 text-white/70 hover:bg-white/5'
|
||||
: 'border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10'"
|
||||
: 'glass-button-warning font-medium'"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
|
||||
@@ -674,15 +674,7 @@
|
||||
<p class="text-sm font-medium" :class="aiPermissions.allEnabled ? 'text-white/95' : 'text-white/70'">{{ t('common.enableAll') }}</p>
|
||||
<p class="text-xs text-white/50 mt-0.5">{{ t('settings.enableAllDesc') }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="w-10 h-6 rounded-full shrink-0 transition-colors relative"
|
||||
:class="aiPermissions.allEnabled ? 'bg-orange-500' : 'bg-white/15'"
|
||||
>
|
||||
<div
|
||||
class="absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
|
||||
:class="aiPermissions.allEnabled ? 'translate-x-5' : 'translate-x-1'"
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch :model-value="aiPermissions.allEnabled" @update:model-value="aiPermissions.allEnabled ? aiPermissions.disableAll() : aiPermissions.enableAll()" @click.stop />
|
||||
</button>
|
||||
|
||||
<div class="space-y-5">
|
||||
@@ -705,15 +697,7 @@
|
||||
<p class="text-sm font-medium" :class="aiPermissions.isEnabled(cat.id) ? 'text-white/95' : 'text-white/70'">{{ cat.label }}</p>
|
||||
<p class="text-xs text-white/50 mt-0.5">{{ cat.description }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="w-10 h-6 rounded-full shrink-0 transition-colors relative"
|
||||
:class="aiPermissions.isEnabled(cat.id) ? 'bg-orange-500' : 'bg-white/15'"
|
||||
>
|
||||
<div
|
||||
class="absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
|
||||
:class="aiPermissions.isEnabled(cat.id) ? 'translate-x-5' : 'translate-x-1'"
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch :model-value="aiPermissions.isEnabled(cat.id)" @update:model-value="aiPermissions.toggle(cat.id)" @click.stop />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -744,22 +728,7 @@
|
||||
<h2 class="text-xl font-semibold text-white/96">{{ t('settings.webhookNotifications') }}</h2>
|
||||
<p class="text-sm text-white/60 mt-1">{{ t('settings.webhookNotificationsDesc') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@click="toggleWebhookEnabled"
|
||||
role="switch"
|
||||
:aria-checked="webhookConfig.enabled"
|
||||
:aria-label="webhookConfig.enabled ? t('settings.disableWebhooks') : t('settings.enableWebhooks')"
|
||||
class="w-10 h-6 rounded-full shrink-0 transition-colors relative"
|
||||
:class="webhookConfig.enabled ? 'bg-orange-500' : 'bg-white/15'"
|
||||
:title="webhookConfig.enabled ? t('settings.disableWebhooks') : t('settings.enableWebhooks')"
|
||||
>
|
||||
<div
|
||||
class="absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
|
||||
:class="webhookConfig.enabled ? 'translate-x-5' : 'translate-x-1'"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<ToggleSwitch :model-value="webhookConfig.enabled" @update:model-value="toggleWebhookEnabled" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -824,7 +793,7 @@
|
||||
<button
|
||||
@click="saveWebhookConfig"
|
||||
:disabled="savingWebhook"
|
||||
class="glass-button px-4 py-2 rounded-lg text-sm flex items-center justify-center gap-2 bg-orange-500/20 border-orange-500/30 disabled:opacity-50"
|
||||
class="glass-button glass-button-warning px-4 py-2 rounded-lg text-sm flex items-center justify-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{{ savingWebhook ? t('settings.savingWebhook') : t('common.saveConfiguration') }}
|
||||
</button>
|
||||
@@ -839,7 +808,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Webhook status message -->
|
||||
<div v-if="webhookStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="webhookStatusType === 'error' ? 'bg-red-500/15 text-red-300' : 'bg-green-500/15 text-green-300'">
|
||||
<div v-if="webhookStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="webhookStatusType === 'error' ? 'alert-error' : 'alert-success'">
|
||||
{{ webhookStatusMsg }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -855,7 +824,7 @@
|
||||
@click="toggleTelemetry"
|
||||
:disabled="telemetryLoading"
|
||||
class="shrink-0 ml-4 px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
:class="telemetryEnabled ? 'bg-green-500/20 text-green-300 border border-green-500/30 hover:bg-green-500/30' : 'glass-button'"
|
||||
:class="telemetryEnabled ? 'glass-button glass-button-success' : 'glass-button'"
|
||||
>
|
||||
{{ telemetryLoading ? '...' : telemetryEnabled ? 'Enabled' : 'Enable' }}
|
||||
</button>
|
||||
@@ -906,7 +875,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Backup status message -->
|
||||
<div v-if="backupStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="backupStatusType === 'error' ? 'bg-red-500/15 text-red-300' : 'bg-green-500/15 text-green-300'">
|
||||
<div v-if="backupStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="backupStatusType === 'error' ? 'alert-error' : 'alert-success'">
|
||||
{{ backupStatusMsg }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -928,7 +897,7 @@
|
||||
</div>
|
||||
<div class="flex gap-3 mt-5">
|
||||
<button @click="showCreateBackupModal = false" class="glass-button px-4 py-2 rounded-lg text-sm flex-1">{{ t('common.cancel') }}</button>
|
||||
<button @click="createBackup" :disabled="creatingBackup || !backupPassphrase" class="glass-button px-4 py-2 rounded-lg text-sm flex-1 bg-orange-500/20 border-orange-500/30 disabled:opacity-50">
|
||||
<button @click="createBackup" :disabled="creatingBackup || !backupPassphrase" class="glass-button glass-button-warning px-4 py-2 rounded-lg text-sm flex-1 disabled:opacity-50">
|
||||
{{ creatingBackup ? t('settings.creatingBackup') : t('settings.createBackup') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -948,7 +917,7 @@
|
||||
</div>
|
||||
<div class="flex gap-3 mt-5">
|
||||
<button @click="showRestoreModal = false" class="glass-button px-4 py-2 rounded-lg text-sm flex-1">{{ t('common.cancel') }}</button>
|
||||
<button @click="restoreBackup" :disabled="restoringBackup || !restorePassphrase" class="glass-button px-4 py-2 rounded-lg text-sm flex-1 bg-red-500/20 border-red-500/30 disabled:opacity-50">
|
||||
<button @click="restoreBackup" :disabled="restoringBackup || !restorePassphrase" class="glass-button glass-button-danger px-4 py-2 rounded-lg text-sm flex-1 disabled:opacity-50">
|
||||
{{ restoringBackup ? t('common.restoring') : t('common.restore') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -979,7 +948,7 @@
|
||||
Wipe all user data, identities, and credentials. Container images are preserved. The node will restart and show the onboarding screen.
|
||||
</p>
|
||||
<button
|
||||
class="glass-button text-red-400 border-red-500/30 hover:border-red-500/50"
|
||||
class="glass-button glass-button-danger"
|
||||
@click="showFactoryResetConfirm = true"
|
||||
>
|
||||
Factory Reset
|
||||
@@ -997,7 +966,7 @@
|
||||
<div class="flex gap-3 justify-end">
|
||||
<button class="glass-button" @click="showFactoryResetConfirm = false">Cancel</button>
|
||||
<button
|
||||
class="glass-button text-red-400 border-red-500/30"
|
||||
class="glass-button glass-button-danger"
|
||||
:disabled="factoryResetLoading"
|
||||
@click="performFactoryReset"
|
||||
>
|
||||
@@ -1020,6 +989,7 @@ import { useAppStore } from '../stores/app'
|
||||
import { useUIModeStore } from '@/stores/uiMode'
|
||||
import { useAIPermissionsStore, AI_PERMISSION_CATEGORIES } from '@/stores/aiPermissions'
|
||||
import ControllerIndicator from '@/components/ControllerIndicator.vue'
|
||||
import ToggleSwitch from '@/components/ToggleSwitch.vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { useModalKeyboard } from '@/composables/useModalKeyboard'
|
||||
import type { UIMode } from '@/types/api'
|
||||
|
||||
Reference in New Issue
Block a user