Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/core/utils/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Future<SSHClient> genClient(
username: alterUser ?? spi.user,
onPasswordRequest: () => spi.pwd,
onUserInfoRequest: onKeyboardInteractive,
onVerifyHostKey: (type, fingerprint) => true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Unconditional host key acceptance creates a security vulnerability.

This callback unconditionally accepts all SSH host keys without verification, completely bypassing a fundamental security mechanism. This exposes users to man-in-the-middle attacks, DNS spoofing, and server impersonation.

Security implications:

  • Attackers can intercept SSH connections without detection
  • Changed host keys (potential compromise indicator) are silently accepted
  • No user warning or consent for this security downgrade

Recommended approach:

  • Store and verify host keys properly (persist fingerprints in secure storage)
  • Prompt users to verify fingerprints on first connection
  • Warn users when host keys change
  • If bypassing is needed for testing, gate it behind a developer setting with clear warnings
- onVerifyHostKey: (type, fingerprint) => true, + onVerifyHostKey: (type, fingerprint) { + // TODO: Implement proper host key verification + // 1. Check if fingerprint is stored for this host + // 2. If first connection, prompt user to verify fingerprint + // 3. If fingerprint changed, warn user of potential MITM attack + // 4. Store accepted fingerprints securely + return _verifyHostKeyWithUserConsent(spi.id, type, fingerprint); + },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/core/utils/server.dart around line 99 the onVerifyHostKey callback currently returns true unconditionally, which accepts any SSH host key and creates a critical MITM risk; replace this with a proper verification flow: load stored host key fingerprints from secure persistent storage, if none exist prompt or surface the fingerprint for first-time user confirmation and then persist it; on subsequent connections compare the presented fingerprint to the stored one and reject the connection (or surface a clear warning and require explicit user confirmation) if it differs; provide a developer-only bypass flag (explicitly gated and logged) for testing only. 
// printDebug: debugPrint,
// printTrace: debugPrint,
);
Expand All @@ -109,6 +110,7 @@ Future<SSHClient> genClient(
// Must use [compute] here, instead of [Computer.shared.start]
identities: await compute(loadIndentity, privateKey),
onUserInfoRequest: onKeyboardInteractive,
onVerifyHostKey: (type, fingerprint) => true,
// printDebug: debugPrint,
// printTrace: debugPrint,
);
Expand Down
5 changes: 1 addition & 4 deletions lib/data/provider/ai/ask_ai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,7 @@ class AskAiRepository {

if (localeHint != null && localeHint.isNotEmpty) {
promptBuffer
..writeln('请优先使用用户的语言输出:$localeHint。')
..writeln('如果无法判断语言,请使用简体中文。');
} else {
promptBuffer.writeln('如果无法判断语言,请使用简体中文。');
.writeln('请优先使用用户的语言输出:$localeHint。');
}

final messages = <Map<String, String>>[
Expand Down
196 changes: 98 additions & 98 deletions lib/generated/l10n/l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,102 @@ abstract class AppLocalizations {
/// **'Already in last directory.'**
String get alreadyLastDir;

/// No description provided for @askAi.
///
/// In en, this message translates to:
/// **'Ask AI'**
String get askAi;

/// No description provided for @askAiApiKey.
///
/// In en, this message translates to:
/// **'API Key'**
String get askAiApiKey;

/// No description provided for @askAiAwaitingResponse.
///
/// In en, this message translates to:
/// **'Waiting for AI response...'**
String get askAiAwaitingResponse;

/// No description provided for @askAiBaseUrl.
///
/// In en, this message translates to:
/// **'Base URL'**
String get askAiBaseUrl;

/// No description provided for @askAiCommandInserted.
///
/// In en, this message translates to:
/// **'Command inserted into terminal'**
String get askAiCommandInserted;

/// No description provided for @askAiConfigMissing.
///
/// In en, this message translates to:
/// **'Please configure {fields} in Settings.'**
String askAiConfigMissing(Object fields);

/// No description provided for @askAiConfirmExecute.
///
/// In en, this message translates to:
/// **'Confirm before executing'**
String get askAiConfirmExecute;

/// No description provided for @askAiConversation.
///
/// In en, this message translates to:
/// **'AI conversation'**
String get askAiConversation;

/// No description provided for @askAiDisclaimer.
///
/// In en, this message translates to:
/// **'AI may be incorrect. Review carefully before applying.'**
String get askAiDisclaimer;

/// No description provided for @askAiFollowUpHint.
///
/// In en, this message translates to:
/// **'Ask a follow-up...'**
String get askAiFollowUpHint;

/// No description provided for @askAiInsertTerminal.
///
/// In en, this message translates to:
/// **'Insert into terminal'**
String get askAiInsertTerminal;

/// No description provided for @askAiModel.
///
/// In en, this message translates to:
/// **'Model'**
String get askAiModel;

/// No description provided for @askAiNoResponse.
///
/// In en, this message translates to:
/// **'No response'**
String get askAiNoResponse;

/// No description provided for @askAiRecommendedCommand.
///
/// In en, this message translates to:
/// **'AI suggested command'**
String get askAiRecommendedCommand;

/// No description provided for @askAiSelectedContent.
///
/// In en, this message translates to:
/// **'Selected content'**
String get askAiSelectedContent;

/// No description provided for @askAiUsageHint.
///
/// In en, this message translates to:
/// **'Used in SSH Terminal'**
String get askAiUsageHint;

/// No description provided for @atLeastOneTab.
///
/// In en, this message translates to:
Expand Down Expand Up @@ -285,13 +381,13 @@ abstract class AppLocalizations {
///
/// In en, this message translates to:
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
String clearServerStatsContent(String serverName);
String clearServerStatsContent(Object serverName);

/// No description provided for @clearServerStatsTitle.
///
/// In en, this message translates to:
/// **'Clear {serverName} Statistics'**
String clearServerStatsTitle(String serverName);
String clearServerStatsTitle(Object serverName);

/// No description provided for @clearThisServerStats.
///
Expand Down Expand Up @@ -1747,102 +1843,6 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'**
String get writeScriptTip;

/// No description provided for @askAi.
///
/// In en, this message translates to:
/// **'Ask AI'**
String get askAi;

/// No description provided for @askAiUsageHint.
///
/// In en, this message translates to:
/// **'Used in SSH Terminal'**
String get askAiUsageHint;

/// No description provided for @askAiBaseUrl.
///
/// In en, this message translates to:
/// **'Base URL'**
String get askAiBaseUrl;

/// No description provided for @askAiModel.
///
/// In en, this message translates to:
/// **'Model'**
String get askAiModel;

/// No description provided for @askAiApiKey.
///
/// In en, this message translates to:
/// **'API Key'**
String get askAiApiKey;

/// No description provided for @askAiConfigMissing.
///
/// In en, this message translates to:
/// **'Please configure {fields} in Settings.'**
String askAiConfigMissing(String fields);

/// No description provided for @askAiConfirmExecute.
///
/// In en, this message translates to:
/// **'Confirm before executing'**
String get askAiConfirmExecute;

/// No description provided for @askAiCommandInserted.
///
/// In en, this message translates to:
/// **'Command inserted into terminal'**
String get askAiCommandInserted;

/// No description provided for @askAiAwaitingResponse.
///
/// In en, this message translates to:
/// **'Waiting for AI response...'**
String get askAiAwaitingResponse;

/// No description provided for @askAiNoResponse.
///
/// In en, this message translates to:
/// **'No response'**
String get askAiNoResponse;

/// No description provided for @askAiRecommendedCommand.
///
/// In en, this message translates to:
/// **'AI suggested command'**
String get askAiRecommendedCommand;

/// No description provided for @askAiInsertTerminal.
///
/// In en, this message translates to:
/// **'Insert into terminal'**
String get askAiInsertTerminal;

/// No description provided for @askAiSelectedContent.
///
/// In en, this message translates to:
/// **'Selected content'**
String get askAiSelectedContent;

/// No description provided for @askAiConversation.
///
/// In en, this message translates to:
/// **'AI conversation'**
String get askAiConversation;

/// No description provided for @askAiFollowUpHint.
///
/// In en, this message translates to:
/// **'Ask a follow-up...'**
String get askAiFollowUpHint;

/// No description provided for @askAiSend.
///
/// In en, this message translates to:
/// **'Send'**
String get askAiSend;
}

class _AppLocalizationsDelegate
Expand Down
105 changes: 53 additions & 52 deletions lib/generated/l10n/l10n_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,57 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get alreadyLastDir => 'Bereits im letzten Verzeichnis.';

@override
String get askAi => 'KI fragen';

@override
String get askAiApiKey => 'API-Schlüssel';

@override
String get askAiAwaitingResponse => 'Warte auf KI-Antwort...';

@override
String get askAiBaseUrl => 'Basis-URL';

@override
String get askAiCommandInserted => 'Befehl ins Terminal eingefügt';

@override
String askAiConfigMissing(Object fields) {
return 'Bitte konfigurieren Sie $fields in den Einstellungen.';
}

@override
String get askAiConfirmExecute => 'Vor Ausführung bestätigen';

@override
String get askAiConversation => 'KI-Unterhaltung';

@override
String get askAiDisclaimer =>
'KI kann Fehler machen. Bitte vorsichtig verwenden.';

@override
String get askAiFollowUpHint => 'Weitere Frage stellen...';

@override
String get askAiInsertTerminal => 'In Terminal einfügen';

@override
String get askAiModel => 'Modell';

@override
String get askAiNoResponse => 'Keine Antwort';

@override
String get askAiRecommendedCommand => 'KI-empfohlener Befehl';

@override
String get askAiSelectedContent => 'Ausgewählter Inhalt';

@override
String get askAiUsageHint => 'Verwendet im SSH-Terminal';

@override
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';

Expand Down Expand Up @@ -99,12 +150,12 @@ class AppLocalizationsDe extends AppLocalizations {
String get clearAllStatsTitle => 'Alle Statistiken löschen';

@override
String clearServerStatsContent(String serverName) {
String clearServerStatsContent(Object serverName) {
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
}

@override
String clearServerStatsTitle(String serverName) {
String clearServerStatsTitle(Object serverName) {
return '$serverName Statistiken löschen';
}

Expand Down Expand Up @@ -923,54 +974,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get writeScriptTip =>
'Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.';

@override
String get askAi => 'KI fragen';

@override
String get askAiUsageHint => 'Verwendet im SSH-Terminal';

@override
String get askAiBaseUrl => 'Basis-URL';

@override
String get askAiModel => 'Modell';

@override
String get askAiApiKey => 'API-Schlüssel';

@override
String askAiConfigMissing(String fields) {
return 'Bitte konfigurieren Sie $fields in den Einstellungen.';
}

@override
String get askAiConfirmExecute => 'Vor Ausführung bestätigen';

@override
String get askAiCommandInserted => 'Befehl ins Terminal eingefügt';

@override
String get askAiAwaitingResponse => 'Warte auf KI-Antwort...';

@override
String get askAiNoResponse => 'Keine Antwort';

@override
String get askAiRecommendedCommand => 'KI-empfohlener Befehl';

@override
String get askAiInsertTerminal => 'In Terminal einfügen';

@override
String get askAiSelectedContent => 'Ausgewählter Inhalt';

@override
String get askAiConversation => 'KI-Unterhaltung';

@override
String get askAiFollowUpHint => 'Weitere Frage stellen...';

@override
String get askAiSend => 'Senden';
}
Loading
Loading