Add SSH key generation and deployment features #234

Merged
ZacharyZcR merged 6 commits from main into dev-1.7.0 2025-09-15 02:29:32 +00:00
ZacharyZcR commented 2025-09-14 23:04:48 +00:00 (Migrated from github.com)

Summary

  • Implement complete SSH key generation system for ED25519, ECDSA, and RSA key types
  • Add SSH key deployment feature with automatic credential resolution
  • Support passphrase protection for generated keys with auto-fill functionality
  • Implement secure deployment to target hosts with comprehensive error handling

Features Added

SSH Key Generation

  • Native key generation: Direct ssh2 library integration for ED25519, ECDSA, RSA
  • Passphrase support: Optional key password protection with automatic form filling
  • SSH format output: All keys generated in proper SSH format (not PEM)
  • Type detection: Automatic key type detection and validation
  • UI integration: Three-button interface with comprehensive error handling

SSH Key Deployment

  • Multi-auth support: Deploy to hosts using password, key, or credential authentication
  • Credential resolution: Automatic resolution of credential-based host configurations
  • Smart deployment: Check for existing keys to prevent duplicates
  • Secure process: Four-step deployment with verification (mkdir → check → deploy → verify)
  • UI workflow: Host selection interface with deployment progress tracking

Technical Improvements

  • Type safety: Enhanced TypeScript interfaces with publicKey field support
  • Error handling: Comprehensive error handling and user feedback
  • API consistency: RESTful endpoints following existing patterns
  • Security: Proper SSH connection handling with timeouts and cleanup

Test plan

  • Test ED25519 key generation with and without passphrase
  • Test ECDSA key generation with proper curve parameters
  • Test RSA key generation with configurable bit length
  • Test SSH key deployment to password-authenticated hosts
  • Test SSH key deployment to key-authenticated hosts
  • Test SSH key deployment to credential-authenticated hosts
  • Test deployment error handling and timeout scenarios
  • Test UI responsiveness and error feedback
  • Verify all generated keys are in proper SSH format
  • Verify deployment verification process works correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Complete SSH key management: separate private/public keys, live type detection, and validation.
    • Generate key pairs (Ed25519, ECDSA P‑256, RSA) with optional passphrase; derive public key from private key.
    • Deploy a credential’s public key to a selected host from the UI with progress feedback.
    • Revamped Credential Editor: upload/paste keys, auto-detection, friendly type labels, and public key handling.
  • Improvements

    • Credential details now include public key and detected key type.
    • SSH connections prioritize the stored private key for better compatibility.
    • More consistent error propagation across credential APIs.
## Summary - Implement complete SSH key generation system for ED25519, ECDSA, and RSA key types - Add SSH key deployment feature with automatic credential resolution - Support passphrase protection for generated keys with auto-fill functionality - Implement secure deployment to target hosts with comprehensive error handling ## Features Added ### SSH Key Generation - **Native key generation**: Direct ssh2 library integration for ED25519, ECDSA, RSA - **Passphrase support**: Optional key password protection with automatic form filling - **SSH format output**: All keys generated in proper SSH format (not PEM) - **Type detection**: Automatic key type detection and validation - **UI integration**: Three-button interface with comprehensive error handling ### SSH Key Deployment - **Multi-auth support**: Deploy to hosts using password, key, or credential authentication - **Credential resolution**: Automatic resolution of credential-based host configurations - **Smart deployment**: Check for existing keys to prevent duplicates - **Secure process**: Four-step deployment with verification (mkdir → check → deploy → verify) - **UI workflow**: Host selection interface with deployment progress tracking ### Technical Improvements - **Type safety**: Enhanced TypeScript interfaces with publicKey field support - **Error handling**: Comprehensive error handling and user feedback - **API consistency**: RESTful endpoints following existing patterns - **Security**: Proper SSH connection handling with timeouts and cleanup ## Test plan - [x] Test ED25519 key generation with and without passphrase - [x] Test ECDSA key generation with proper curve parameters - [x] Test RSA key generation with configurable bit length - [x] Test SSH key deployment to password-authenticated hosts - [x] Test SSH key deployment to key-authenticated hosts - [x] Test SSH key deployment to credential-authenticated hosts - [x] Test deployment error handling and timeout scenarios - [x] Test UI responsiveness and error feedback - [x] Verify all generated keys are in proper SSH format - [x] Verify deployment verification process works correctly 🤖 Generated with [Claude Code](https://claude.ai/code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Complete SSH key management: separate private/public keys, live type detection, and validation. * Generate key pairs (Ed25519, ECDSA P‑256, RSA) with optional passphrase; derive public key from private key. * Deploy a credential’s public key to a selected host from the UI with progress feedback. * Revamped Credential Editor: upload/paste keys, auto-detection, friendly type labels, and public key handling. * **Improvements** * Credential details now include public key and detected key type. * SSH connections prioritize the stored private key for better compatibility. * More consistent error propagation across credential APIs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2025-09-14 23:04:54 +00:00 (Migrated from github.com)

Walkthrough

Adds SSH key pair support across backend, UI, and types: database schema gains private/public key fields; backend routes add key parsing, detection, generation, validation, and deploy-to-host endpoints; SSH connection code prefers privateKey; new SSH key utilities module; UI updates for key generation/detection and deployment; API wrappers added.

Changes

Cohort / File(s) Summary
Database schema
src/backend/database/db/schema.ts
Adds columns to ssh_credentials: private_key, public_key, detected_key_type; retains key with backward-compatibility note.
SSH key utilities
src/backend/utils/ssh-key-utils.ts
New module for parsing private/public keys, type detection, key-pair validation, and friendly names; exposes parseSSHKey, parsePublicKey, detectKeyType, getFriendlyKeyTypeName, validateKeyPair.
Credentials API routes
src/backend/database/routes/credentials.ts
Extends create/update/read to handle privateKey/publicKey/detectedKeyType; adds endpoints: detect key type (private/public), validate key pair, generate key pair, derive public key, and deploy public key to host.
SSH client usage
src/backend/ssh/terminal.ts, src/backend/ssh/tunnel.ts
Prefers credential.privateKey over credential.key when setting SSH connect config key.
Shared types
src/types/index.ts
Adds optional publicKey?: string to Credential and CredentialData.
UI — Credential editor
src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx, .../unified_key_section.tsx
Overhauls key management UI: generate key pair, derive public key, live type detection for private/public keys, updated form to include publicKey.
UI — Credentials manager
src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx
Adds deploy-to-host flow for credential’s public key with selection UI and progress handling.
UI API wrappers
src/ui/main-axios.ts
Adds API helpers: detect key type (private/public), validate pair, generate public key, generate key pair, deploy credential to host; standardizes error handling to rethrow processed errors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CE as CredentialsEditor (UI)
  participant AX as main-axios
  participant BE as Credentials Routes
  participant DB as DB (ssh_credentials)
  participant SK as ssh-key-utils

  rect rgb(245,248,255)
  note over U,CE: Generate Key Pair flow
  U->>CE: Click "Generate Key Pair"
  CE->>AX: POST /credentials/generate-key-pair {type, size?, passphrase?}
  AX->>BE: generate-key-pair
  BE->>SK: generate via ssh2 / helpers
  SK-->>BE: {privateKey, publicKey, keyType}
  BE-->>AX: {privateKey, publicKey, keyType}
  AX-->>CE: {privateKey, publicKey}
  CE->>CE: Populate form fields
  end

  rect rgb(245,255,245)
  note over U,CE: Create/Update Credential with keys
  U->>CE: Submit form (key, publicKey, keyPassword?)
  CE->>AX: POST/PUT /credentials {key, publicKey, ...}
  AX->>BE: create/update
  BE->>SK: parseSSHKey / parsePublicKey / detectKeyType
  SK-->>BE: {detectedKeyType, normalized publicKey}
  BE->>DB: store {private_key|key, public_key, detected_key_type}
  DB-->>BE: ok
  BE-->>AX: saved credential
  AX-->>CE: response
  end

  rect rgb(255,248,245)
  note over U,CE: On-demand detection/derivation
  U->>CE: Paste key / Click "Generate Public Key"
  CE->>AX: POST /credentials/detect-key-type or /generate-public-key
  AX->>BE: route call
  BE->>SK: detect / derive
  SK-->>BE: result
  BE-->>AX: result
  AX-->>CE: Update UI indicators/publicKey
  end
sequenceDiagram
  autonumber
  actor U as User
  participant CM as CredentialsManager (UI)
  participant AX as main-axios
  participant BE as Credentials Routes
  participant DB as DB
  participant H as Target Host (SSH)

  rect rgb(245,255,250)
  note over U,CM: Deploy public key to host
  U->>CM: Select credential + host, click Deploy
  CM->>AX: POST /credentials/{id}/deploy-to-host {targetHostId}
  AX->>BE: deploy-to-host
  BE->>DB: fetch credential (private_key/public_key)
  DB-->>BE: credential
  BE->>H: SSH session using host creds
  BE->>H: mkdir -p ~/.ssh && append to authorized_keys
  H-->>BE: success/verify
  BE-->>AX: deployment result
  AX-->>CM: success/failure
  CM->>U: Toast result
  end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • LukeGus/Termix#221 — Earlier work around SSH credentials and schema; this PR extends the same areas with additional key fields and key-management flows.

Pre-merge checks and finishing touches

Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.83% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Passed checks (2 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check Passed The title "Add SSH key generation and deployment features" succinctly captures the primary scope of the changeset—adding SSH key generation, detection/validation, UI integration, and deployment support—and aligns with the detailed file and objective summaries; it is specific, on-topic, and free of noisy or vague phrasing.
Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

- Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
- Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- walkthrough_start --> ## Walkthrough Adds SSH key pair support across backend, UI, and types: database schema gains private/public key fields; backend routes add key parsing, detection, generation, validation, and deploy-to-host endpoints; SSH connection code prefers privateKey; new SSH key utilities module; UI updates for key generation/detection and deployment; API wrappers added. ## Changes | Cohort / File(s) | Summary | | --- | --- | | **Database schema**<br>`src/backend/database/db/schema.ts` | Adds columns to ssh_credentials: `private_key`, `public_key`, `detected_key_type`; retains `key` with backward-compatibility note. | | **SSH key utilities**<br>`src/backend/utils/ssh-key-utils.ts` | New module for parsing private/public keys, type detection, key-pair validation, and friendly names; exposes `parseSSHKey`, `parsePublicKey`, `detectKeyType`, `getFriendlyKeyTypeName`, `validateKeyPair`. | | **Credentials API routes**<br>`src/backend/database/routes/credentials.ts` | Extends create/update/read to handle privateKey/publicKey/detectedKeyType; adds endpoints: detect key type (private/public), validate key pair, generate key pair, derive public key, and deploy public key to host. | | **SSH client usage**<br>`src/backend/ssh/terminal.ts`, `src/backend/ssh/tunnel.ts` | Prefers `credential.privateKey` over `credential.key` when setting SSH connect config key. | | **Shared types**<br>`src/types/index.ts` | Adds optional `publicKey?: string` to `Credential` and `CredentialData`. | | **UI — Credential editor**<br>`src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx`, `.../unified_key_section.tsx` | Overhauls key management UI: generate key pair, derive public key, live type detection for private/public keys, updated form to include `publicKey`. | | **UI — Credentials manager**<br>`src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx` | Adds deploy-to-host flow for credential’s public key with selection UI and progress handling. | | **UI API wrappers**<br>`src/ui/main-axios.ts` | Adds API helpers: detect key type (private/public), validate pair, generate public key, generate key pair, deploy credential to host; standardizes error handling to rethrow processed errors. | ## Sequence Diagram(s) ```mermaid sequenceDiagram autonumber actor U as User participant CE as CredentialsEditor (UI) participant AX as main-axios participant BE as Credentials Routes participant DB as DB (ssh_credentials) participant SK as ssh-key-utils rect rgb(245,248,255) note over U,CE: Generate Key Pair flow U->>CE: Click "Generate Key Pair" CE->>AX: POST /credentials/generate-key-pair {type, size?, passphrase?} AX->>BE: generate-key-pair BE->>SK: generate via ssh2 / helpers SK-->>BE: {privateKey, publicKey, keyType} BE-->>AX: {privateKey, publicKey, keyType} AX-->>CE: {privateKey, publicKey} CE->>CE: Populate form fields end rect rgb(245,255,245) note over U,CE: Create/Update Credential with keys U->>CE: Submit form (key, publicKey, keyPassword?) CE->>AX: POST/PUT /credentials {key, publicKey, ...} AX->>BE: create/update BE->>SK: parseSSHKey / parsePublicKey / detectKeyType SK-->>BE: {detectedKeyType, normalized publicKey} BE->>DB: store {private_key|key, public_key, detected_key_type} DB-->>BE: ok BE-->>AX: saved credential AX-->>CE: response end rect rgb(255,248,245) note over U,CE: On-demand detection/derivation U->>CE: Paste key / Click "Generate Public Key" CE->>AX: POST /credentials/detect-key-type or /generate-public-key AX->>BE: route call BE->>SK: detect / derive SK-->>BE: result BE-->>AX: result AX-->>CE: Update UI indicators/publicKey end ``` ```mermaid sequenceDiagram autonumber actor U as User participant CM as CredentialsManager (UI) participant AX as main-axios participant BE as Credentials Routes participant DB as DB participant H as Target Host (SSH) rect rgb(245,255,250) note over U,CM: Deploy public key to host U->>CM: Select credential + host, click Deploy CM->>AX: POST /credentials/{id}/deploy-to-host {targetHostId} AX->>BE: deploy-to-host BE->>DB: fetch credential (private_key/public_key) DB-->>BE: credential BE->>H: SSH session using host creds BE->>H: mkdir -p ~/.ssh && append to authorized_keys H-->>BE: success/verify BE-->>AX: deployment result AX-->>CM: success/failure CM->>U: Toast result end ``` ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~75 minutes ## Possibly related PRs - LukeGus/Termix#221 — Earlier work around SSH credentials and schema; this PR extends the same areas with additional key fields and key-management flows. <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> ## Pre-merge checks and finishing touches <details> <summary>❌ Failed checks (1 warning)</summary> | Check name | Status | Explanation | Resolution | | :----------------: | :--------- | :------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------- | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 44.83% which is insufficient. The required threshold is 80.00%. | You can run `@coderabbitai generate docstrings` to improve docstring coverage. | </details> <details> <summary>✅ Passed checks (2 passed)</summary> | Check name | Status | Explanation | | :---------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title Check | ✅ Passed | The title "Add SSH key generation and deployment features" succinctly captures the primary scope of the changeset—adding SSH key generation, detection/validation, UI integration, and deployment support—and aligns with the detailed file and objective summaries; it is specific, on-topic, and free of noisy or vague phrasing. | </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing touches</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings <details> <summary>🧪 Generate unit tests</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Post copyable unit tests in a comment </details> </details> <!-- finishing_touch_checkbox_end --> <!-- announcements_start --> > [!TIP] > <details> > <summary>👮 Agentic pre-merge checks are now available in preview!</summary> > > Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs. > > - Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more. > - Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks. > > Please see the [documentation](https://docs.coderabbit.ai/pr-reviews/pre-merge-checks) for more information. > > Example: > > ```yaml > reviews: > pre_merge_checks: > custom_checks: > - name: "Undocumented Breaking Changes" > mode: "warning" > instructions: | > Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). > ``` > > Please share your feedback with us on this [Discord post](https://discord.com/channels/1134356397673414807/1414771631775158383). > > </details> <!-- announcements_end --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2025-09-14 23:13:16 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/backend/ssh/tunnel.ts (3)

618-626: Endpoint key passphrase is unsupported here — will hang/fail.

The spawned ssh -i … cannot consume a passphrase non‑interactively. If resolvedEndpointCredentials.keyPassword is set, the tunnel connection will fail or block. Add a pre‑flight check and error out with a clear message, or implement an agent‑based approach (load key via ssh-agent on the source host) before starting the reverse tunnel.

Apply this guard:

-    if (
-      resolvedEndpointCredentials.authMethod === "key" &&
-      resolvedEndpointCredentials.sshKey
-    ) {
+    if (
+      resolvedEndpointCredentials.authMethod === "key" &&
+      resolvedEndpointCredentials.sshKey
+    ) {
+      if (resolvedEndpointCredentials.keyPassword) {
+        tunnelLogger.error(
+          `Endpoint key for '${tunnelName}' requires a passphrase, which is not supported for non-interactive reverse tunnels.`,
+        );
+        broadcastTunnelStatus(tunnelName, {
+          connected: false,
+          status: CONNECTION_STATES.FAILED,
+          reason: "Endpoint key with passphrase not supported for tunnels",
+        });
+        conn.end();
+        return;
+      }

622-626: Harden key write and ensure cleanup even on failure.

Avoid raw echo of multi‑line key and guarantee deletion with a trap. Also safely escape passwords for the sshpass path.

Apply this refactor:

-      const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
-      tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} ${tunnelMarker} && rm -f ${keyFilePath}`;
+      const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
+      const keyB64 = Buffer.from(resolvedEndpointCredentials.sshKey, "utf8").toString("base64");
+      tunnelCmd =
+        `set -euo pipefail; ` +
+        `trap 'rm -f ${keyFilePath}' EXIT; ` +
+        `base64 -d > ${keyFilePath} << 'EOF'\n${keyB64}\nEOF\n` +
+        `chmod 600 ${keyFilePath}; ` +
+        `ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} ${tunnelMarker}`;

And for the password path:

-      tunnelCmd = `sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -N ...`;
+      const pw = (resolvedEndpointCredentials.password || "").replace(/'/g, `'\\''`);
+      tunnelCmd = `sshpass -p '${pw}' ssh -N ...`;

745-768: Drop insecure algorithms (CBC, MD5, and old DH groups).

These weaken the security posture and are unnecessary for modern servers. Keep CTR/GCM ciphers and SHA2 MACs; drop *_cbc, 3des-cbc, hmac-md5, diffie-hellman-group1-sha1, and *-sha1 KEX.

Apply this tightening:

       algorithms: {
-      kex: [
-        "diffie-hellman-group14-sha256",
-        "diffie-hellman-group14-sha1",
-        "diffie-hellman-group1-sha1",
-        "diffie-hellman-group-exchange-sha256",
-        "diffie-hellman-group-exchange-sha1",
-        "ecdh-sha2-nistp256",
-        "ecdh-sha2-nistp384",
-        "ecdh-sha2-nistp521",
-      ],
-      cipher: [
-        "aes128-ctr",
-        "aes192-ctr",
-        "aes256-ctr",
-        "aes128-gcm@openssh.com",
-        "aes256-gcm@openssh.com",
-        "aes128-cbc",
-        "aes192-cbc",
-        "aes256-cbc",
-        "3des-cbc",
-      ],
-      hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-md5"],
+      kex: [
+        "diffie-hellman-group14-sha256",
+        "diffie-hellman-group-exchange-sha256",
+        "ecdh-sha2-nistp256",
+        "ecdh-sha2-nistp384",
+        "ecdh-sha2-nistp521",
+      ],
+      cipher: [
+        "aes128-ctr",
+        "aes192-ctr",
+        "aes256-ctr",
+        "aes128-gcm@openssh.com",
+        "aes256-gcm@openssh.com",
+      ],
+      hmac: ["hmac-sha2-256", "hmac-sha2-512"],
src/backend/database/routes/credentials.ts (2)

71-89: JWT secret defaulting to “secret”.

Do not default to a weak secret. Fail fast if JWT_SECRET is missing.

- const jwtSecret = process.env.JWT_SECRET || "secret";
+ const jwtSecret = process.env.JWT_SECRET;
+ if (!jwtSecret) {
+   authLogger.error("JWT_SECRET is not configured");
+   return res.status(500).json({ error: "Server misconfiguration" });
+ }

309-331: Sensitive fields exposure in GET /credentials/:id.

You return key, privateKey, and keyPassword in plaintext. If this is required, ensure strict ownership checks (already present) and consider an explicit includeSecrets flag or separate endpoint.

src/ui/main-axios.ts (1)

1509-1552: Unify error handling style; remove redundant “throw handleApiError(...).”

handleApiError throws; using “throw handleApiError(...)” is redundant and inconsistent across functions.

- } catch (error) {
-   throw handleApiError(error, "fetch credentials");
- }
+ } catch (error) {
+   handleApiError(error, "fetch credentials");
+ }

Apply similarly to other new wrappers for consistency.

Also applies to: 1555-1561, 1660-1668, 1670-1760

🧹 Nitpick comments (16)
src/backend/ssh/terminal.ts (1)

416-421: privateKeyType is not a supported ssh2 option.

ssh2 infers the key type from the key data. This field is ignored; remove to avoid confusion.

-          connectConfig.privateKeyType = resolvedCredentials.keyType;
+          // ssh2 infers key type from the private key; no explicit option needed
src/backend/utils/ssh-key-utils.ts (2)

55-60: Avoid guessing RSA for OpenSSH keys when undecidable.

Defaulting to RSA risks misclassification. Return unknown instead.

-      // Default to RSA for OpenSSH format if we can't detect specifically
-      return 'ssh-rsa';
+      // Unable to detect reliably
+      return 'unknown';

216-223: Consider removing privateKey from the KeyInfo return.

Reduce accidental propagation of secrets; callers already have the input.

-export interface KeyInfo {
-  privateKey: string;
+export interface KeyInfo {
   publicKey: string;
   keyType: string;
   success: boolean;
   error?: string;
 }

And update parseSSHKey to omit echoing back the private key.

unified_key_section.tsx (4)

21-38: Tighten file input accept list (drop wildcard) and handle multi-line keys safely.

  • accept="*,..." allows any file; prefer explicit extensions only to avoid accidental uploads.
  • When reading uploaded keys, trim and normalize line endings to prevent stray CR/LF from breaking downstream parsing.

Apply:

- accept="*,.pem,.key,.txt,.ppk"
+ accept=".pem,.key,.txt,.ppk"

And after file.text():

- const fileContent = await file.text();
+ const fileContent = (await file.text()).replace(/\r\n?/g, '\n').trim();

Also applies to: 145-159


85-89: Use theme tokens instead of hard-coded colors.

Replace text-green-600 with tokenized classes (e.g., text-success or via CSS var) per the theme system.

Also applies to: 115-118, 204-208


114-118: i18n: localize “Key type:” label.

Wrap static text in t().

- <span className="text-muted-foreground">Key type: </span>
+ <span className="text-muted-foreground">{t("credentials.keyType")}:</span>

201-215: Avoid calling form.watch() inside JSX conditionals repeatedly.

Store const publicKeyVal = form.watch("publicKey") once above and use it to reduce re-renders.

src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx (3)

128-140: Precondition UX: show actionable hint when publicKey missing.

Current toast is fine; consider offering a CTA to open editor to generate the public key.


789-926: i18n and theme tokens in deploy sheet.

  • Wrap all static strings (“Deploy SSH Key”, “Target Host”, “Deployment Process”, button labels) with t().
  • Replace hard-coded colors (bg-green-600, text-green-600) with theme tokens.

142-169: Propagate backend failure messages to the user.

When result.success is false, include result.error in the toast for quicker debugging.

- toast.error(result.error || "Deployment failed");
+ toast.error(result.error ? `Deployment failed: ${result.error}` : "Deployment failed");
src/backend/database/routes/credentials.ts (2)

881-909: Key pair generation response fields: align algorithm metadata.

algorithm and keyType are redundant; consider a single normalized keyType and add curve/size only when applicable.


1257-1396: Add audit logs with minimal metadata only.

On deploy success/failure, log credentialId/hostId and userId; never log key contents.

src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (4)

49-56: Timeout refs: use ReturnType instead of NodeJS.Timeout.

Prevents TS/node DOM type mismatch in the browser.

- const keyDetectionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+ const keyDetectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

- const publicKeyDetectionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+ const publicKeyDetectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

Also applies to: 53-56, 220-230


256-264: Debounce robustness: cancel pending timers on rapid edits.

You clear existing timers—good. Also consider leading-edge detection when pasting long keys to speed up UX.

Also applies to: 290-298


786-900: Private/public key uploads: normalize content and trim.

Normalize CRLF and trim before detection to reduce false negatives.

- const fileContent = await file.text();
+ const fileContent = (await file.text()).replace(/\r\n?/g, '\n').trim();

Also applies to: 903-935, 913-931


334-347: Submit payload: avoid sending nulls for optional fields.

Prefer omitting undefined/empty fields over explicit nulls to reduce backend branching.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f75cd4d16 and a674073ec8.

Files ignored due to path filters (2)
  • src/locales/en/translation.json is excluded by !**/*.json
  • src/locales/zh/translation.json is excluded by !**/*.json
📒 Files selected for processing (10)
  • src/backend/database/db/schema.ts (1 hunks)
  • src/backend/database/routes/credentials.ts (7 hunks)
  • src/backend/ssh/terminal.ts (1 hunks)
  • src/backend/ssh/tunnel.ts (2 hunks)
  • src/backend/utils/ssh-key-utils.ts (1 hunks)
  • src/types/index.ts (2 hunks)
  • src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (10 hunks)
  • src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx (6 hunks)
  • src/ui/main-axios.ts (9 hunks)
  • unified_key_section.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

**/*.{ts,tsx}: Review TypeScript and React code for Termix server management platform. Key considerations:

Architecture & Patterns:

  • Follow the established multi-port backend architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085)
  • Use proper separation between Desktop and Mobile UI components
  • Maintain consistent state management patterns with React hooks and context
  • Follow the established tab-based navigation system

Database & Backend:

  • Use Drizzle ORM with SQLite for database operations
  • Implement proper JWT authentication middleware patterns
  • Follow the established API error handling patterns in main-axios.ts
  • Use proper logging with the structured logger system (apiLogger, authLogger, sshLogger, etc.)
  • Maintain proper input validation and sanitization

UI/UX Guidelines:

  • Use Shadcn/UI components with Tailwind CSS for consistent styling
  • Follow the established theme system with dark/light mode support
  • Use proper responsive design patterns for Desktop/Mobile views
  • Implement proper loading states and error handling
  • Use the established confirmation patterns with useConfirmation hook
  • Use CSS variables and classes from index.css instead of hardcoding colors
  • Follow the established color token system (--primary, --secondary, --background, etc.)
  • Use proper Tailwind CSS classes instead of inline styles
  • Implement proper focus states and accessibility indicators

SSH & Security:

  • Implement proper SSH connection management with session handling
  • Use secure credential storage and management patterns
  • Follow the established authentication flow (password, key, credential-based)
  • Implement proper file operation security and validation

Code Quality:

  • Use proper TypeScript types from the centralized types/index.ts
  • Follow the established API patterns in main-axios.ts
  • Implement proper error boundaries and fallback UI
  • Use proper React patterns (hooks, context, refs)
  • Maintain consistent naming conventions...

Files:

  • src/backend/ssh/terminal.ts
  • src/types/index.ts
  • src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx
  • src/backend/ssh/tunnel.ts
  • src/backend/utils/ssh-key-utils.ts
  • src/backend/database/db/schema.ts
  • src/backend/database/routes/credentials.ts
  • unified_key_section.tsx
  • src/ui/main-axios.ts
  • src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx
**/backend/**/*.{ts,js}

⚙️ CodeRabbit configuration file

**/backend/**/*.{ts,js}: Review backend code for Termix server management platform. Key considerations:

Backend Architecture:

  • Follow the multi-port microservice architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085)
  • Use Express.js with proper middleware patterns
  • Implement proper CORS and security headers
  • Use proper request/response logging with structured logging

Database Operations:

  • Use Drizzle ORM with proper schema definitions
  • Implement proper database migrations and schema updates
  • Use proper transaction handling for critical operations
  • Follow the established database connection patterns

Authentication & Security:

  • Implement proper JWT token validation and refresh
  • Use bcryptjs for password hashing with proper salt rounds
  • Implement proper input validation and sanitization
  • Use proper CORS configuration for security
  • Implement proper rate limiting and security headers

SSH Operations:

  • Use ssh2 library with proper connection management
  • Implement proper SSH key handling and validation
  • Use proper session management and cleanup
  • Implement proper error handling for SSH operations
  • Use proper file operation security and validation

API Design:

  • Follow RESTful API patterns with proper HTTP status codes
  • Implement proper error response formatting
  • Use proper request/response validation
  • Implement proper API versioning and backward compatibility
  • All API routes should be defined in main-axios.ts, not scattered across components
  • Use the established multi-port API architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085)
  • Follow the established error handling patterns with handleApiError function
  • Use proper structured logging with service-specific loggers (apiLogger, authLogger, sshLogger, etc.)

Logging & Monitoring:

  • Use the structured logging system with proper context
  • Implement proper error tracking and reporting
  • Use proper performance monitoring and metrics
  • Impleme...

Files:

  • src/backend/ssh/terminal.ts
  • src/backend/ssh/tunnel.ts
  • src/backend/utils/ssh-key-utils.ts
  • src/backend/database/db/schema.ts
  • src/backend/database/routes/credentials.ts
**/types/**/*.{ts,js}

⚙️ CodeRabbit configuration file

**/types/**/*.{ts,js}: Review type definitions for Termix server management platform. Key considerations:

Type Design:

  • Use proper TypeScript interfaces and type definitions
  • Implement proper type safety and validation
  • Use proper generic types and utility types
  • Follow the established type naming conventions

API Types:

  • Define proper request/response types for all API endpoints
  • Use proper error types and status codes
  • Implement proper validation types and schemas
  • Use proper pagination and filtering types

SSH Types:

  • Define proper SSH connection and configuration types
  • Use proper tunnel and credential types
  • Implement proper file operation types
  • Use proper authentication and security types

Type Safety:

  • Ensure proper type coverage and completeness
  • Use proper strict type checking
  • Implement proper type narrowing and guards
  • Use proper type assertions and casting

Highlight any type safety issues, missing types, or type inconsistencies.

Files:

  • src/types/index.ts
**/main-axios.ts

⚙️ CodeRabbit configuration file

**/main-axios.ts: Review main-axios.ts API client configuration for Termix server management platform. Key considerations:

API Client Architecture:

  • Maintain the multi-port API architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085)
  • Use proper service-specific API instances (sshHostApi, tunnelApi, fileManagerApi, statsApi, authApi)
  • Implement proper API instance creation with createApiInstance function
  • Use proper base URL configuration for different environments (dev, production, Electron)

Error Handling:

  • Use the centralized handleApiError function for consistent error handling
  • Implement proper error classification (auth, network, validation, server errors)
  • Use proper error logging with service-specific loggers
  • Implement proper error response formatting and user-friendly messages

Request/Response Interceptors:

  • Implement proper JWT token handling in request interceptors
  • Use proper request timing and performance logging
  • Implement proper response logging and error tracking
  • Use proper authentication token refresh and cleanup

API Function Organization:

  • Group API functions by service (SSH Host Management, Tunnel Management, File Manager, etc.)
  • Use proper TypeScript types for all API functions
  • Implement proper parameter validation and sanitization
  • Use proper return type definitions and error handling

Authentication:

  • Implement proper JWT token management and refresh
  • Use proper cookie handling for web and Electron environments
  • Implement proper authentication state management
  • Use proper token expiration and cleanup

Logging:

  • Use proper structured logging with context and metadata
  • Implement proper request/response logging with performance metrics
  • Use proper error logging with appropriate log levels
  • Implement proper service-specific logger selection

Performance:

  • Implement proper request timeout and retry logic
  • Use proper connection pooling and resource management
  • Implement pr...

Files:

  • src/ui/main-axios.ts
🧬 Code graph analysis (6)
src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx (2)
src/types/index.ts (1)
  • Credential (63-80)
src/ui/main-axios.ts (2)
  • getSSHHosts (591-598)
  • deployCredentialToHost (1747-1760)
src/backend/utils/ssh-key-utils.ts (1)
src/ui/main-axios.ts (2)
  • detectKeyType (1670-1683)
  • validateKeyPair (1698-1713)
src/backend/database/routes/credentials.ts (2)
src/backend/utils/ssh-key-utils.ts (3)
  • parseSSHKey (242-363)
  • parsePublicKey (368-391)
  • validateKeyPair (430-527)
src/backend/database/db/schema.ts (1)
  • sshCredentials (128-154)
unified_key_section.tsx (1)
src/backend/utils/ssh-key-utils.ts (1)
  • getFriendlyKeyTypeName (411-425)
src/ui/main-axios.ts (1)
src/backend/utils/ssh-key-utils.ts (2)
  • detectKeyType (396-406)
  • validateKeyPair (430-527)
src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (3)
src/ui/main-axios.ts (4)
  • detectKeyType (1670-1683)
  • detectPublicKeyType (1685-1696)
  • generateKeyPair (1730-1745)
  • generatePublicKeyFromPrivate (1715-1728)
src/backend/utils/ssh-key-utils.ts (2)
  • detectKeyType (396-406)
  • getFriendlyKeyTypeName (411-425)
src/components/ui/password-input.tsx (1)
  • PasswordInput (11-39)
🪛 Gitleaks (8.27.2)
src/backend/utils/ssh-key-utils.ts

[high] 10-31: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)


[high] 64-67: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)


[high] 70-75: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)


[high] 79-148: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

src/backend/database/routes/credentials.ts

[high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

🔇 Additional comments (9)
src/backend/ssh/tunnel.ts (2)

458-462: Good: prefer privateKey over legacy key.

Back‑compat fallback is correct and matches the new schema.


504-508: Good: endpoint credentials also prefer privateKey.

Keeps behavior consistent between source and endpoint paths.

src/types/index.ts (1)

73-76: Adding publicKey to Credential types looks good.

Matches the new schema and UI flows. Ensure we never propagate private key material to these interfaces accidentally.

If not already covered, please confirm the UI uses publicKey only for display/deploy and that any logs redact it.

Also applies to: 91-94

src/backend/ssh/terminal.ts (1)

194-198: Good: prefer privateKey over legacy key.

Brings terminal auth resolution in line with the new credential fields.

src/backend/utils/ssh-key-utils.ts (1)

396-406: Allow passphrase in detectKeyType and propagate to callers.

Encrypted private keys parse as 'unknown' without a passphrase — add an optional passphrase parameter and forward it to ssh2Utils.parseKey.

-export function detectKeyType(privateKeyData: string): string {
+export function detectKeyType(privateKeyData: string, passphrase?: string): string {
   try {
-    const parsedKey = ssh2Utils.parseKey(privateKeyData);
+    const parsedKey = ssh2Utils.parseKey(privateKeyData, passphrase);

Ensure route callers pass the passphrase where available.

unified_key_section.tsx (1)

45-49: Ensure Generate Public Key handler supports File and string for key.

If key is a File (from upload), handleGeneratePublicKey must read its text before calling the API. Please confirm or adapt the handler.

Would you like me to patch handleGeneratePublicKey to read File values?

Also applies to: 239-264

src/ui/main-axios.ts (1)

591-598: getSSHHosts: add explicit return type in error path.

Function signature promises SSHHost[]; after handleApiError throws, TS infers never, but for clarity, keep the signature and rely on the throw. No action required.

src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (1)

673-788: Great: integrated generation paths with auto-fill and detection.

Solid UX flow: passphrase auto-fill, immediate detection, and toasts on result.

src/backend/database/routes/credentials.ts (1)

1257-1396: Automated check — ensure user scoping on deploy route selects

Verification failed: the repository search returned no results. Confirm that both DB selects in src/backend/database/routes/credentials.ts include user scoping.

  • Credential select: add user scoping alongside the id check, e.g. where(eq(sshCredentials.id, credentialId), eq(sshCredentials.userId, req.user.id)).
  • Target host select: add user scoping alongside the id check, e.g. where(eq(sshData.id, targetHostId), eq(sshData.userId, req.user.id)).

If already added, paste the two select statements here or re-run:
rg -nP -C2 "from(sshCredentials)[\s\S]*where(" src/backend/database/routes/credentials.ts && rg -nP -C2 "from(sshData)[\s\S]*where(" src/backend/database/routes/credentials.ts

**Actionable comments posted: 16** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to platform limitations. > > > > <details> > <summary>⚠️ Outside diff range comments (6)</summary><blockquote> > > <details> > <summary>src/backend/ssh/tunnel.ts (3)</summary><blockquote> > > `618-626`: **Endpoint key passphrase is unsupported here — will hang/fail.** > > The spawned `ssh -i …` cannot consume a passphrase non‑interactively. If `resolvedEndpointCredentials.keyPassword` is set, the tunnel connection will fail or block. Add a pre‑flight check and error out with a clear message, or implement an agent‑based approach (load key via `ssh-agent` on the source host) before starting the reverse tunnel. > > Apply this guard: > > ```diff > - if ( > - resolvedEndpointCredentials.authMethod === "key" && > - resolvedEndpointCredentials.sshKey > - ) { > + if ( > + resolvedEndpointCredentials.authMethod === "key" && > + resolvedEndpointCredentials.sshKey > + ) { > + if (resolvedEndpointCredentials.keyPassword) { > + tunnelLogger.error( > + `Endpoint key for '${tunnelName}' requires a passphrase, which is not supported for non-interactive reverse tunnels.`, > + ); > + broadcastTunnelStatus(tunnelName, { > + connected: false, > + status: CONNECTION_STATES.FAILED, > + reason: "Endpoint key with passphrase not supported for tunnels", > + }); > + conn.end(); > + return; > + } > ``` > > --- > > `622-626`: **Harden key write and ensure cleanup even on failure.** > > Avoid raw echo of multi‑line key and guarantee deletion with a trap. Also safely escape passwords for the sshpass path. > > Apply this refactor: > > ```diff > - const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`; > - tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} ${tunnelMarker} && rm -f ${keyFilePath}`; > + const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`; > + const keyB64 = Buffer.from(resolvedEndpointCredentials.sshKey, "utf8").toString("base64"); > + tunnelCmd = > + `set -euo pipefail; ` + > + `trap 'rm -f ${keyFilePath}' EXIT; ` + > + `base64 -d > ${keyFilePath} << 'EOF'\n${keyB64}\nEOF\n` + > + `chmod 600 ${keyFilePath}; ` + > + `ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} ${tunnelMarker}`; > ``` > > And for the password path: > > ```diff > - tunnelCmd = `sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -N ...`; > + const pw = (resolvedEndpointCredentials.password || "").replace(/'/g, `'\\''`); > + tunnelCmd = `sshpass -p '${pw}' ssh -N ...`; > ``` > > --- > > `745-768`: **Drop insecure algorithms (CBC, MD5, and old DH groups).** > > These weaken the security posture and are unnecessary for modern servers. Keep CTR/GCM ciphers and SHA2 MACs; drop `*_cbc`, `3des-cbc`, `hmac-md5`, `diffie-hellman-group1-sha1`, and `*-sha1` KEX. > > Apply this tightening: > > ```diff > algorithms: { > - kex: [ > - "diffie-hellman-group14-sha256", > - "diffie-hellman-group14-sha1", > - "diffie-hellman-group1-sha1", > - "diffie-hellman-group-exchange-sha256", > - "diffie-hellman-group-exchange-sha1", > - "ecdh-sha2-nistp256", > - "ecdh-sha2-nistp384", > - "ecdh-sha2-nistp521", > - ], > - cipher: [ > - "aes128-ctr", > - "aes192-ctr", > - "aes256-ctr", > - "aes128-gcm@openssh.com", > - "aes256-gcm@openssh.com", > - "aes128-cbc", > - "aes192-cbc", > - "aes256-cbc", > - "3des-cbc", > - ], > - hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-md5"], > + kex: [ > + "diffie-hellman-group14-sha256", > + "diffie-hellman-group-exchange-sha256", > + "ecdh-sha2-nistp256", > + "ecdh-sha2-nistp384", > + "ecdh-sha2-nistp521", > + ], > + cipher: [ > + "aes128-ctr", > + "aes192-ctr", > + "aes256-ctr", > + "aes128-gcm@openssh.com", > + "aes256-gcm@openssh.com", > + ], > + hmac: ["hmac-sha2-256", "hmac-sha2-512"], > ``` > > </blockquote></details> > <details> > <summary>src/backend/database/routes/credentials.ts (2)</summary><blockquote> > > `71-89`: **JWT secret defaulting to “secret”.** > > Do not default to a weak secret. Fail fast if JWT_SECRET is missing. > > ```diff > - const jwtSecret = process.env.JWT_SECRET || "secret"; > + const jwtSecret = process.env.JWT_SECRET; > + if (!jwtSecret) { > + authLogger.error("JWT_SECRET is not configured"); > + return res.status(500).json({ error: "Server misconfiguration" }); > + } > ``` > > --- > > `309-331`: **Sensitive fields exposure in GET /credentials/:id.** > > You return key, privateKey, and keyPassword in plaintext. If this is required, ensure strict ownership checks (already present) and consider an explicit includeSecrets flag or separate endpoint. > > </blockquote></details> > <details> > <summary>src/ui/main-axios.ts (1)</summary><blockquote> > > `1509-1552`: **Unify error handling style; remove redundant “throw handleApiError(...).”** > > handleApiError throws; using “throw handleApiError(...)” is redundant and inconsistent across functions. > > ```diff > - } catch (error) { > - throw handleApiError(error, "fetch credentials"); > - } > + } catch (error) { > + handleApiError(error, "fetch credentials"); > + } > ``` > Apply similarly to other new wrappers for consistency. > > > Also applies to: 1555-1561, 1660-1668, 1670-1760 > > </blockquote></details> > > </blockquote></details> <details> <summary>🧹 Nitpick comments (16)</summary><blockquote> <details> <summary>src/backend/ssh/terminal.ts (1)</summary><blockquote> `416-421`: **`privateKeyType` is not a supported ssh2 option.** ssh2 infers the key type from the key data. This field is ignored; remove to avoid confusion. ```diff - connectConfig.privateKeyType = resolvedCredentials.keyType; + // ssh2 infers key type from the private key; no explicit option needed ``` </blockquote></details> <details> <summary>src/backend/utils/ssh-key-utils.ts (2)</summary><blockquote> `55-60`: **Avoid guessing RSA for OpenSSH keys when undecidable.** Defaulting to RSA risks misclassification. Return `unknown` instead. ```diff - // Default to RSA for OpenSSH format if we can't detect specifically - return 'ssh-rsa'; + // Unable to detect reliably + return 'unknown'; ``` --- `216-223`: **Consider removing `privateKey` from the `KeyInfo` return.** Reduce accidental propagation of secrets; callers already have the input. ```diff -export interface KeyInfo { - privateKey: string; +export interface KeyInfo { publicKey: string; keyType: string; success: boolean; error?: string; } ``` And update `parseSSHKey` to omit echoing back the private key. </blockquote></details> <details> <summary>unified_key_section.tsx (4)</summary><blockquote> `21-38`: **Tighten file input accept list (drop wildcard) and handle multi-line keys safely.** - accept="*,..." allows any file; prefer explicit extensions only to avoid accidental uploads. - When reading uploaded keys, trim and normalize line endings to prevent stray CR/LF from breaking downstream parsing. Apply: ```diff - accept="*,.pem,.key,.txt,.ppk" + accept=".pem,.key,.txt,.ppk" ``` And after file.text(): ```diff - const fileContent = await file.text(); + const fileContent = (await file.text()).replace(/\r\n?/g, '\n').trim(); ``` Also applies to: 145-159 --- `85-89`: **Use theme tokens instead of hard-coded colors.** Replace text-green-600 with tokenized classes (e.g., text-success or via CSS var) per the theme system. Also applies to: 115-118, 204-208 --- `114-118`: **i18n: localize “Key type:” label.** Wrap static text in t(). ```diff - <span className="text-muted-foreground">Key type: </span> + <span className="text-muted-foreground">{t("credentials.keyType")}:</span> ``` --- `201-215`: **Avoid calling form.watch() inside JSX conditionals repeatedly.** Store const publicKeyVal = form.watch("publicKey") once above and use it to reduce re-renders. </blockquote></details> <details> <summary>src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx (3)</summary><blockquote> `128-140`: **Precondition UX: show actionable hint when publicKey missing.** Current toast is fine; consider offering a CTA to open editor to generate the public key. --- `789-926`: **i18n and theme tokens in deploy sheet.** - Wrap all static strings (“Deploy SSH Key”, “Target Host”, “Deployment Process”, button labels) with t(). - Replace hard-coded colors (bg-green-600, text-green-600) with theme tokens. --- `142-169`: **Propagate backend failure messages to the user.** When result.success is false, include result.error in the toast for quicker debugging. ```diff - toast.error(result.error || "Deployment failed"); + toast.error(result.error ? `Deployment failed: ${result.error}` : "Deployment failed"); ``` </blockquote></details> <details> <summary>src/backend/database/routes/credentials.ts (2)</summary><blockquote> `881-909`: **Key pair generation response fields: align algorithm metadata.** algorithm and keyType are redundant; consider a single normalized keyType and add curve/size only when applicable. --- `1257-1396`: **Add audit logs with minimal metadata only.** On deploy success/failure, log credentialId/hostId and userId; never log key contents. </blockquote></details> <details> <summary>src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (4)</summary><blockquote> `49-56`: **Timeout refs: use ReturnType<typeof setTimeout> instead of NodeJS.Timeout.** Prevents TS/node DOM type mismatch in the browser. ```diff - const keyDetectionTimeoutRef = useRef<NodeJS.Timeout | null>(null); + const keyDetectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); - const publicKeyDetectionTimeoutRef = useRef<NodeJS.Timeout | null>(null); + const publicKeyDetectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); ``` Also applies to: 53-56, 220-230 --- `256-264`: **Debounce robustness: cancel pending timers on rapid edits.** You clear existing timers—good. Also consider leading-edge detection when pasting long keys to speed up UX. Also applies to: 290-298 --- `786-900`: **Private/public key uploads: normalize content and trim.** Normalize CRLF and trim before detection to reduce false negatives. ```diff - const fileContent = await file.text(); + const fileContent = (await file.text()).replace(/\r\n?/g, '\n').trim(); ``` Also applies to: 903-935, 913-931 --- `334-347`: **Submit payload: avoid sending nulls for optional fields.** Prefer omitting undefined/empty fields over explicit nulls to reduce backend branching. </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used**: Path: .coderabbit.yaml **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 0f75cd4d16c7f59c9f52ec26b22e589425842cd9 and a674073ec8416bf56f128959d0ccdb95d9eee89c. </details> <details> <summary>⛔ Files ignored due to path filters (2)</summary> * `src/locales/en/translation.json` is excluded by `!**/*.json` * `src/locales/zh/translation.json` is excluded by `!**/*.json` </details> <details> <summary>📒 Files selected for processing (10)</summary> * `src/backend/database/db/schema.ts` (1 hunks) * `src/backend/database/routes/credentials.ts` (7 hunks) * `src/backend/ssh/terminal.ts` (1 hunks) * `src/backend/ssh/tunnel.ts` (2 hunks) * `src/backend/utils/ssh-key-utils.ts` (1 hunks) * `src/types/index.ts` (2 hunks) * `src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx` (10 hunks) * `src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx` (6 hunks) * `src/ui/main-axios.ts` (9 hunks) * `unified_key_section.tsx` (1 hunks) </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>📓 Path-based instructions (4)</summary> <details> <summary>**/*.{ts,tsx}</summary> **⚙️ CodeRabbit configuration file** > `**/*.{ts,tsx}`: Review TypeScript and React code for Termix server management platform. Key considerations: > > **Architecture & Patterns:** > - Follow the established multi-port backend architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085) > - Use proper separation between Desktop and Mobile UI components > - Maintain consistent state management patterns with React hooks and context > - Follow the established tab-based navigation system > > **Database & Backend:** > - Use Drizzle ORM with SQLite for database operations > - Implement proper JWT authentication middleware patterns > - Follow the established API error handling patterns in main-axios.ts > - Use proper logging with the structured logger system (apiLogger, authLogger, sshLogger, etc.) > - Maintain proper input validation and sanitization > > **UI/UX Guidelines:** > - Use Shadcn/UI components with Tailwind CSS for consistent styling > - Follow the established theme system with dark/light mode support > - Use proper responsive design patterns for Desktop/Mobile views > - Implement proper loading states and error handling > - Use the established confirmation patterns with useConfirmation hook > - Use CSS variables and classes from index.css instead of hardcoding colors > - Follow the established color token system (--primary, --secondary, --background, etc.) > - Use proper Tailwind CSS classes instead of inline styles > - Implement proper focus states and accessibility indicators > > **SSH & Security:** > - Implement proper SSH connection management with session handling > - Use secure credential storage and management patterns > - Follow the established authentication flow (password, key, credential-based) > - Implement proper file operation security and validation > > **Code Quality:** > - Use proper TypeScript types from the centralized types/index.ts > - Follow the established API patterns in main-axios.ts > - Implement proper error boundaries and fallback UI > - Use proper React patterns (hooks, context, refs) > - Maintain consistent naming conventions... Files: - `src/backend/ssh/terminal.ts` - `src/types/index.ts` - `src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx` - `src/backend/ssh/tunnel.ts` - `src/backend/utils/ssh-key-utils.ts` - `src/backend/database/db/schema.ts` - `src/backend/database/routes/credentials.ts` - `unified_key_section.tsx` - `src/ui/main-axios.ts` - `src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx` </details> <details> <summary>**/backend/**/*.{ts,js}</summary> **⚙️ CodeRabbit configuration file** > `**/backend/**/*.{ts,js}`: Review backend code for Termix server management platform. Key considerations: > > **Backend Architecture:** > - Follow the multi-port microservice architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085) > - Use Express.js with proper middleware patterns > - Implement proper CORS and security headers > - Use proper request/response logging with structured logging > > **Database Operations:** > - Use Drizzle ORM with proper schema definitions > - Implement proper database migrations and schema updates > - Use proper transaction handling for critical operations > - Follow the established database connection patterns > > **Authentication & Security:** > - Implement proper JWT token validation and refresh > - Use bcryptjs for password hashing with proper salt rounds > - Implement proper input validation and sanitization > - Use proper CORS configuration for security > - Implement proper rate limiting and security headers > > **SSH Operations:** > - Use ssh2 library with proper connection management > - Implement proper SSH key handling and validation > - Use proper session management and cleanup > - Implement proper error handling for SSH operations > - Use proper file operation security and validation > > **API Design:** > - Follow RESTful API patterns with proper HTTP status codes > - Implement proper error response formatting > - Use proper request/response validation > - Implement proper API versioning and backward compatibility > - All API routes should be defined in main-axios.ts, not scattered across components > - Use the established multi-port API architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085) > - Follow the established error handling patterns with handleApiError function > - Use proper structured logging with service-specific loggers (apiLogger, authLogger, sshLogger, etc.) > > **Logging & Monitoring:** > - Use the structured logging system with proper context > - Implement proper error tracking and reporting > - Use proper performance monitoring and metrics > - Impleme... Files: - `src/backend/ssh/terminal.ts` - `src/backend/ssh/tunnel.ts` - `src/backend/utils/ssh-key-utils.ts` - `src/backend/database/db/schema.ts` - `src/backend/database/routes/credentials.ts` </details> <details> <summary>**/types/**/*.{ts,js}</summary> **⚙️ CodeRabbit configuration file** > `**/types/**/*.{ts,js}`: Review type definitions for Termix server management platform. Key considerations: > > **Type Design:** > - Use proper TypeScript interfaces and type definitions > - Implement proper type safety and validation > - Use proper generic types and utility types > - Follow the established type naming conventions > > **API Types:** > - Define proper request/response types for all API endpoints > - Use proper error types and status codes > - Implement proper validation types and schemas > - Use proper pagination and filtering types > > **SSH Types:** > - Define proper SSH connection and configuration types > - Use proper tunnel and credential types > - Implement proper file operation types > - Use proper authentication and security types > > **Type Safety:** > - Ensure proper type coverage and completeness > - Use proper strict type checking > - Implement proper type narrowing and guards > - Use proper type assertions and casting > > Highlight any type safety issues, missing types, or type inconsistencies. > Files: - `src/types/index.ts` </details> <details> <summary>**/main-axios.ts</summary> **⚙️ CodeRabbit configuration file** > `**/main-axios.ts`: Review main-axios.ts API client configuration for Termix server management platform. Key considerations: > > **API Client Architecture:** > - Maintain the multi-port API architecture (SSH: 8081, Tunnel: 8083, File Manager: 8084, Stats: 8085) > - Use proper service-specific API instances (sshHostApi, tunnelApi, fileManagerApi, statsApi, authApi) > - Implement proper API instance creation with createApiInstance function > - Use proper base URL configuration for different environments (dev, production, Electron) > > **Error Handling:** > - Use the centralized handleApiError function for consistent error handling > - Implement proper error classification (auth, network, validation, server errors) > - Use proper error logging with service-specific loggers > - Implement proper error response formatting and user-friendly messages > > **Request/Response Interceptors:** > - Implement proper JWT token handling in request interceptors > - Use proper request timing and performance logging > - Implement proper response logging and error tracking > - Use proper authentication token refresh and cleanup > > **API Function Organization:** > - Group API functions by service (SSH Host Management, Tunnel Management, File Manager, etc.) > - Use proper TypeScript types for all API functions > - Implement proper parameter validation and sanitization > - Use proper return type definitions and error handling > > **Authentication:** > - Implement proper JWT token management and refresh > - Use proper cookie handling for web and Electron environments > - Implement proper authentication state management > - Use proper token expiration and cleanup > > **Logging:** > - Use proper structured logging with context and metadata > - Implement proper request/response logging with performance metrics > - Use proper error logging with appropriate log levels > - Implement proper service-specific logger selection > > **Performance:** > - Implement proper request timeout and retry logic > - Use proper connection pooling and resource management > - Implement pr... Files: - `src/ui/main-axios.ts` </details> </details><details> <summary>🧬 Code graph analysis (6)</summary> <details> <summary>src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx (2)</summary><blockquote> <details> <summary>src/types/index.ts (1)</summary> * `Credential` (63-80) </details> <details> <summary>src/ui/main-axios.ts (2)</summary> * `getSSHHosts` (591-598) * `deployCredentialToHost` (1747-1760) </details> </blockquote></details> <details> <summary>src/backend/utils/ssh-key-utils.ts (1)</summary><blockquote> <details> <summary>src/ui/main-axios.ts (2)</summary> * `detectKeyType` (1670-1683) * `validateKeyPair` (1698-1713) </details> </blockquote></details> <details> <summary>src/backend/database/routes/credentials.ts (2)</summary><blockquote> <details> <summary>src/backend/utils/ssh-key-utils.ts (3)</summary> * `parseSSHKey` (242-363) * `parsePublicKey` (368-391) * `validateKeyPair` (430-527) </details> <details> <summary>src/backend/database/db/schema.ts (1)</summary> * `sshCredentials` (128-154) </details> </blockquote></details> <details> <summary>unified_key_section.tsx (1)</summary><blockquote> <details> <summary>src/backend/utils/ssh-key-utils.ts (1)</summary> * `getFriendlyKeyTypeName` (411-425) </details> </blockquote></details> <details> <summary>src/ui/main-axios.ts (1)</summary><blockquote> <details> <summary>src/backend/utils/ssh-key-utils.ts (2)</summary> * `detectKeyType` (396-406) * `validateKeyPair` (430-527) </details> </blockquote></details> <details> <summary>src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (3)</summary><blockquote> <details> <summary>src/ui/main-axios.ts (4)</summary> * `detectKeyType` (1670-1683) * `detectPublicKeyType` (1685-1696) * `generateKeyPair` (1730-1745) * `generatePublicKeyFromPrivate` (1715-1728) </details> <details> <summary>src/backend/utils/ssh-key-utils.ts (2)</summary> * `detectKeyType` (396-406) * `getFriendlyKeyTypeName` (411-425) </details> <details> <summary>src/components/ui/password-input.tsx (1)</summary> * `PasswordInput` (11-39) </details> </blockquote></details> </details><details> <summary>🪛 Gitleaks (8.27.2)</summary> <details> <summary>src/backend/utils/ssh-key-utils.ts</summary> [high] 10-31: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) --- [high] 64-67: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) --- [high] 70-75: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) --- [high] 79-148: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) </details> <details> <summary>src/backend/database/routes/credentials.ts</summary> [high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) </details> </details> </details> <details> <summary>🔇 Additional comments (9)</summary><blockquote> <details> <summary>src/backend/ssh/tunnel.ts (2)</summary><blockquote> `458-462`: **Good: prefer `privateKey` over legacy `key`.** Back‑compat fallback is correct and matches the new schema. --- `504-508`: **Good: endpoint credentials also prefer `privateKey`.** Keeps behavior consistent between source and endpoint paths. </blockquote></details> <details> <summary>src/types/index.ts (1)</summary><blockquote> `73-76`: **Adding `publicKey` to Credential types looks good.** Matches the new schema and UI flows. Ensure we never propagate private key material to these interfaces accidentally. If not already covered, please confirm the UI uses `publicKey` only for display/deploy and that any logs redact it. Also applies to: 91-94 </blockquote></details> <details> <summary>src/backend/ssh/terminal.ts (1)</summary><blockquote> `194-198`: **Good: prefer `privateKey` over legacy `key`.** Brings terminal auth resolution in line with the new credential fields. </blockquote></details> <details> <summary>src/backend/utils/ssh-key-utils.ts (1)</summary><blockquote> `396-406`: **Allow passphrase in detectKeyType and propagate to callers.** Encrypted private keys parse as 'unknown' without a passphrase — add an optional passphrase parameter and forward it to ssh2Utils.parseKey. ```diff -export function detectKeyType(privateKeyData: string): string { +export function detectKeyType(privateKeyData: string, passphrase?: string): string { try { - const parsedKey = ssh2Utils.parseKey(privateKeyData); + const parsedKey = ssh2Utils.parseKey(privateKeyData, passphrase); ``` Ensure route callers pass the passphrase where available. </blockquote></details> <details> <summary>unified_key_section.tsx (1)</summary><blockquote> `45-49`: **Ensure Generate Public Key handler supports File and string for key.** If key is a File (from upload), handleGeneratePublicKey must read its text before calling the API. Please confirm or adapt the handler. Would you like me to patch handleGeneratePublicKey to read File values? Also applies to: 239-264 </blockquote></details> <details> <summary>src/ui/main-axios.ts (1)</summary><blockquote> `591-598`: **getSSHHosts: add explicit return type in error path.** Function signature promises SSHHost[]; after handleApiError throws, TS infers never, but for clarity, keep the signature and rely on the throw. No action required. </blockquote></details> <details> <summary>src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx (1)</summary><blockquote> `673-788`: **Great: integrated generation paths with auto-fill and detection.** Solid UX flow: passphrase auto-fill, immediate detection, and toasts on result. </blockquote></details> <details> <summary>src/backend/database/routes/credentials.ts (1)</summary><blockquote> `1257-1396`: **Automated check — ensure user scoping on deploy route selects** Verification failed: the repository search returned no results. Confirm that both DB selects in src/backend/database/routes/credentials.ts include user scoping. - Credential select: add user scoping alongside the id check, e.g. where(eq(sshCredentials.id, credentialId), eq(sshCredentials.userId, req.user.id)). - Target host select: add user scoping alongside the id check, e.g. where(eq(sshData.id, targetHostId), eq(sshData.userId, req.user.id)). If already added, paste the two select statements here or re-run: rg -nP -C2 "from\(sshCredentials\)[\s\S]*where\(" src/backend/database/routes/credentials.ts && rg -nP -C2 "from\(sshData\)[\s\S]*where\(" src/backend/database/routes/credentials.ts </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@@ -142,3 +144,4 @@
keyType: text("key_type"),
detectedKeyType: text("detected_key_type"),
usageCount: integer("usage_count").notNull().default(0),
lastUsed: text("last_used"),
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

🛠️ Refactor suggestion

⚠️ Potential issue

Encrypt private keys at rest; reconsider TEXT length hints.

  • Storing privateKey in plaintext is a high‑risk posture. Encrypt at rest (e.g., libsodium or AES‑GCM with app‑level KEK) and ensure strict access controls.
  • SQLite does not enforce TEXT length; the hints are cosmetic. Consider dropping length or increasing publicKey to avoid truncation risk from long comments.
-  key: text("key", { length: 16384 }), // backward compatibility
-  privateKey: text("private_key", { length: 16384 }),
-  publicKey: text("public_key", { length: 4096 }),
+  key: text("key"), // backward compatibility
+  privateKey: text("private_key"), // store encrypted blob if possible
+  publicKey: text("public_key"),

Also add a migration to backfill privateKey from key and then phase out key.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  key: text("key"), // backward compatibility
  privateKey: text("private_key"), // store encrypted blob if possible
  publicKey: text("public_key"),
  keyPassword: text("key_password"),
  keyType: text("key_type"),
  detectedKeyType: text("detected_key_type"),
  usageCount: integer("usage_count").notNull().default(0),
_🛠️ Refactor suggestion_ _⚠️ Potential issue_ **Encrypt private keys at rest; reconsider TEXT length hints.** - Storing `privateKey` in plaintext is a high‑risk posture. Encrypt at rest (e.g., libsodium or AES‑GCM with app‑level KEK) and ensure strict access controls. - SQLite does not enforce TEXT length; the hints are cosmetic. Consider dropping length or increasing `publicKey` to avoid truncation risk from long comments. ```diff - key: text("key", { length: 16384 }), // backward compatibility - privateKey: text("private_key", { length: 16384 }), - publicKey: text("public_key", { length: 4096 }), + key: text("key"), // backward compatibility + privateKey: text("private_key"), // store encrypted blob if possible + publicKey: text("public_key"), ``` Also add a migration to backfill `privateKey` from `key` and then phase out `key`. <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion key: text("key"), // backward compatibility privateKey: text("private_key"), // store encrypted blob if possible publicKey: text("public_key"), keyPassword: text("key_password"), keyType: text("key_type"), detectedKeyType: text("detected_key_type"), usageCount: integer("usage_count").notNull().default(0), ``` </details> <!-- suggestion_end --> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -8,0 +34,4 @@
options.passphrase = passphrase;
options.cipher = 'aes128-cbc'; // Default cipher for encrypted private keys
}
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

🛠️ Refactor suggestion

⚠️ Potential issue

Weak/default crypto options in key generation.

  • For passphrase-protected keys, don’t force aes128-cbc. Prefer ssh2 defaults (bcrypt KDF + aes256-ctr) or explicitly set stronger cipher.
- if (passphrase && passphrase.trim()) {
-   options.passphrase = passphrase;
-   options.cipher = 'aes128-cbc';
- }
+ if (passphrase && passphrase.trim()) {
+   options.passphrase = passphrase;
+   // rely on ssh2's modern defaults (OpenSSH new-format with bcrypt KDF)
+ }

Also consider exposing ECDSA curves via options.curve instead of bits for clarity.

Also applies to: 38-50

_🛠️ Refactor suggestion_ _⚠️ Potential issue_ **Weak/default crypto options in key generation.** - For passphrase-protected keys, don’t force aes128-cbc. Prefer ssh2 defaults (bcrypt KDF + aes256-ctr) or explicitly set stronger cipher. ```diff - if (passphrase && passphrase.trim()) { - options.passphrase = passphrase; - options.cipher = 'aes128-cbc'; - } + if (passphrase && passphrase.trim()) { + options.passphrase = passphrase; + // rely on ssh2's modern defaults (OpenSSH new-format with bcrypt KDF) + } ``` Also consider exposing ECDSA curves via options.curve instead of bits for clarity. Also applies to: 38-50 <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -664,0 +941,4 @@
try {
console.log("Using Node.js crypto to generate public key from private key...");
console.log("Private key length:", privateKey.length);
console.log("Private key first 100 chars:", privateKey.substring(0, 100));
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

🛠️ Refactor suggestion

⚠️ Potential issue

Secret leakage: verbose logs print key material and lengths. Remove before release.

Logging “first 100 chars” and similar leaks private/public keys. Use structured logs without secrets.

Apply redaction:

- console.log("Private key first 100 chars:", privateKey.substring(0, 100));
+ // Redacted debug removed

- console.log("Generated public key first 100 chars:", publicKeyString.substring(0, 100));
+ // Redacted debug removed

Repeat across all key-handling endpoints. Gate any remaining diagnostics behind a DEBUG flag and never log key contents.

Also applies to: 799-835, 837-879, 891-899, 941-969, 1039-1046, 1055-1066, 1084-1097, 1106-1110

_🛠️ Refactor suggestion_ _⚠️ Potential issue_ **Secret leakage: verbose logs print key material and lengths. Remove before release.** Logging “first 100 chars” and similar leaks private/public keys. Use structured logs without secrets. Apply redaction: ```diff - console.log("Private key first 100 chars:", privateKey.substring(0, 100)); + // Redacted debug removed - console.log("Generated public key first 100 chars:", publicKeyString.substring(0, 100)); + // Redacted debug removed ``` Repeat across all key-handling endpoints. Gate any remaining diagnostics behind a DEBUG flag and never log key contents. Also applies to: 799-835, 837-879, 891-899, 941-969, 1039-1046, 1055-1066, 1084-1097, 1106-1110 <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -664,0 +1264,4 @@
success: false,
error: "Credential ID and target host ID are required"
});
}
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

💡 Verification agent

🧩 Analysis chain

Address Gitleaks finding (private-key).

The flagged lines are due to debug logs printing key material. Removing/redacting them resolves the alert.

Run to confirm no key contents are logged:


🏁 Script executed:

#!/bin/bash
rg -nP -C0 "(first 100 chars|Private key length|Public key.*first 100|console\.log\(.+private key|console\.log\(.+public key)" src/backend | sed -n '1,200p'

Length of output: 2283


Remove/redact debug logs that print SSH private/public key material

Gitleaks flagged console logs that expose key contents. Remove or redact any logs that print keys, substrings, or raw key content; log only presence/status or a hashed/truncated indicator.

  • src/backend/database/routes/credentials.ts:42 — console.log('Private key length:', keyPair.private.length)
  • src/backend/database/routes/credentials.ts:942–944 — console.log("Private key length:", privateKey.length); console.log("Private key first 100 chars:", privateKey.substring(0, 100))
  • src/backend/database/routes/credentials.ts:1023–1024, 1056–1063 — SSH2 fallback/public key type/length and "Generated public key first 100 chars" logs
  • src/backend/utils/ssh-key-utils.ts:270, 292, 432, 476–477 — logs printing generated/provided key lengths/content

Replace with non-sensitive logs (e.g., privateKeyProvided: true, generatedPublicKey: true) or redact values (e.g., "[REDACTED]" or a short hash). Re-run scans after changes.

🧰 Tools
🪛 Gitleaks (8.27.2)

[high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Address Gitleaks finding (private-key).** The flagged lines are due to debug logs printing key material. Removing/redacting them resolves the alert. Run to confirm no key contents are logged: --- 🏁 Script executed: ```shell #!/bin/bash rg -nP -C0 "(first 100 chars|Private key length|Public key.*first 100|console\.log\(.+private key|console\.log\(.+public key)" src/backend | sed -n '1,200p' ``` Length of output: 2283 --- </details> **Remove/redact debug logs that print SSH private/public key material** Gitleaks flagged console logs that expose key contents. Remove or redact any logs that print keys, substrings, or raw key content; log only presence/status or a hashed/truncated indicator. - src/backend/database/routes/credentials.ts:42 — console.log('Private key length:', keyPair.private.length) - src/backend/database/routes/credentials.ts:942–944 — console.log("Private key length:", privateKey.length); console.log("Private key first 100 chars:", privateKey.substring(0, 100)) - src/backend/database/routes/credentials.ts:1023–1024, 1056–1063 — SSH2 fallback/public key type/length and "Generated public key first 100 chars" logs - src/backend/utils/ssh-key-utils.ts:270, 292, 432, 476–477 — logs printing generated/provided key lengths/content Replace with non-sensitive logs (e.g., privateKeyProvided: true, generatedPublicKey: true) or redact values (e.g., "[REDACTED]" or a short hash). Re-run scans after changes. <details> <summary>🧰 Tools</summary> <details> <summary>🪛 Gitleaks (8.27.2)</summary> [high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) </details> </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

⚠️ Potential issue

generate-public-key may return PEM; enforce SSH format or fail.

The UI and PR promise SSH-format output. If ssh2 conversion fails, returning PEM breaks deployment. Prefer returning 400 with guidance rather than a PEM fallback.

- let finalPublicKey = publicKeyString; // PEM fallback
- let formatType = 'pem';
+ let finalPublicKey = '';
+ let formatType = 'ssh';
...
- } catch (sshError) {
-   console.warn("ssh2 failed, using PEM format");
- }
+ } catch (sshError) {
+   return res.status(400).json({
+     success: false,
+     error: "Unable to derive SSH-format public key from the provided private key"
+   });
+ }

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

🧰 Tools
🪛 Gitleaks (8.27.2)

[high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

🤖 Prompt for AI Agents
In src/backend/database/routes/credentials.ts around lines 926 to 1120, the
endpoint currently falls back to returning a PEM public key if ssh2 conversion
fails; change this so the endpoint only returns an SSH-formatted public key and
returns a 400 error (with helpful guidance and parseAttempts details) when ssh2
conversion fails instead of returning PEM. Concretely: remove/stop using the PEM
fallback as the finalPublicKey; after attempting ssh2Utils.parseKey, if it fails
or returns an Error respond with res.status(400).json({ success:false, error:
"Unable to produce SSH-format public key; provide an OpenSSH private key or
correct passphrase", details: parseAttempts }) (include any parseAttempts
collected), and only return success when ssh2 produced the SSH public key; keep
existing logging but adjust messages to reflect the hard failure path.
_⚠️ Potential issue_ **generate-public-key may return PEM; enforce SSH format or fail.** The UI and PR promise SSH-format output. If ssh2 conversion fails, returning PEM breaks deployment. Prefer returning 400 with guidance rather than a PEM fallback. ```diff - let finalPublicKey = publicKeyString; // PEM fallback - let formatType = 'pem'; + let finalPublicKey = ''; + let formatType = 'ssh'; ... - } catch (sshError) { - console.warn("ssh2 failed, using PEM format"); - } + } catch (sshError) { + return res.status(400).json({ + success: false, + error: "Unable to derive SSH-format public key from the provided private key" + }); + } ``` > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🧰 Tools</summary> <details> <summary>🪛 Gitleaks (8.27.2)</summary> [high] 986-1000: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption. (private-key) </details> </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/database/routes/credentials.ts around lines 926 to 1120, the endpoint currently falls back to returning a PEM public key if ssh2 conversion fails; change this so the endpoint only returns an SSH-formatted public key and returns a 400 error (with helpful guidance and parseAttempts details) when ssh2 conversion fails instead of returning PEM. Concretely: remove/stop using the PEM fallback as the finalPublicKey; after attempting ssh2Utils.parseKey, if it fails or returns an Error respond with res.status(400).json({ success:false, error: "Unable to produce SSH-format public key; provide an OpenSSH private key or correct passphrase", details: parseAttempts }) (include any parseAttempts collected), and only return success when ssh2 produced the SSH public key; keep existing logging but adjust messages to reflect the hard failure path. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -664,0 +1345,4 @@
} else if (cred.authType === 'key') {
hostConfig.privateKey = cred.privateKey || cred.key; // Try both fields
hostConfig.keyPassword = cred.keyPassword;
}
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

🛠️ Refactor suggestion

⚠️ Potential issue

Command injection risk and fragile grep in deployment.

  • echo with single-quote escaping is insufficient if publicKey contains newlines; prefer SFTP or at minimum printf + grep -F.
  • Use fixed-string grep (-Fq) on key data, not regex.

Apply:

- const keyPattern = publicKey.split(' ')[1]; // Get the key part without algorithm
- conn.exec(`grep -q "${keyPattern}" ~/.ssh/authorized_keys 2>/dev/null`, ...
+ const keyPattern = publicKey.split(' ')[1] || '';
+ conn.exec(`grep -Fq "${keyPattern}" ~/.ssh/authorized_keys 2>/dev/null`, ...

- const escapedKey = publicKey.replace(/'/g, "'\\''");
- conn.exec(`echo '${escapedKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`, ...
+ const sanitized = publicKey.replace(/\r?\n/g, '').trim();
+ const escaped = sanitized.replace(/'/g, "'\\''");
+ conn.exec(`printf '%s\\n' '${escaped}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`, ...

Recommended: replace shell appends with SFTP append to ~/.ssh/authorized_keys to eliminate shell injection entirely.

Also applies to: 1191-1200, 1176-1189

🤖 Prompt for AI Agents
In src/backend/database/routes/credentials.ts around lines 1123-1201 (and
similarly at 1176-1189 and 1191-1200), the current deploySSHKeyToHost uses shell
echo with ad-hoc single-quote escaping and plain grep which is vulnerable to
command-injection and breaks on keys containing newlines; replace the
shell-based append and grep with SFTP-based file operations and fixed-string
grep: open ~/.ssh/authorized_keys via the SSH client's sftp() API (create ~/.ssh
with proper mode first), read and check for the key using a fixed-string search
(or use grep -Fq if you must exec), and if missing append the publicKey via an
SFTP write/append stream and set file mode to 600—this eliminates shell
interpolation and handles newlines safely while ensuring grep uses -Fq for
fixed-string matching.
_🛠️ Refactor suggestion_ _⚠️ Potential issue_ **Command injection risk and fragile grep in deployment.** - echo with single-quote escaping is insufficient if publicKey contains newlines; prefer SFTP or at minimum printf + grep -F. - Use fixed-string grep (-Fq) on key data, not regex. Apply: ```diff - const keyPattern = publicKey.split(' ')[1]; // Get the key part without algorithm - conn.exec(`grep -q "${keyPattern}" ~/.ssh/authorized_keys 2>/dev/null`, ... + const keyPattern = publicKey.split(' ')[1] || ''; + conn.exec(`grep -Fq "${keyPattern}" ~/.ssh/authorized_keys 2>/dev/null`, ... - const escapedKey = publicKey.replace(/'/g, "'\\''"); - conn.exec(`echo '${escapedKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`, ... + const sanitized = publicKey.replace(/\r?\n/g, '').trim(); + const escaped = sanitized.replace(/'/g, "'\\''"); + conn.exec(`printf '%s\\n' '${escaped}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`, ... ``` Recommended: replace shell appends with SFTP append to ~/.ssh/authorized_keys to eliminate shell injection entirely. Also applies to: 1191-1200, 1176-1189 <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/database/routes/credentials.ts around lines 1123-1201 (and similarly at 1176-1189 and 1191-1200), the current deploySSHKeyToHost uses shell echo with ad-hoc single-quote escaping and plain grep which is vulnerable to command-injection and breaks on keys containing newlines; replace the shell-based append and grep with SFTP-based file operations and fixed-string grep: open ~/.ssh/authorized_keys via the SSH client's sftp() API (create ~/.ssh with proper mode first), read and check for the key using a fixed-string search (or use grep -Fq if you must exec), and if missing append the publicKey via an SFTP write/append stream and set file mode to 600—this eliminates shell interpolation and handles newlines safely while ensuring grep uses -Fq for fixed-string matching. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

🛠️ Refactor suggestion

Add connection timeouts to execs and cleanly handle stream errors.

Wrap exec calls with error and stderr handlers; ensure reject on non-zero and capture stderr for diagnostics.

_🛠️ Refactor suggestion_ **Add connection timeouts to execs and cleanly handle stream errors.** Wrap exec calls with error and stderr handlers; ensure reject on non-zero and capture stderr for diagnostics. <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:14 +00:00

⚠️ Potential issue

Authorization bypass: deploy-to-host lacks user scoping for both credential and host. Blocker.

Endpoint does not restrict credential/host to the authenticated user. An attacker could deploy keys to another user’s hosts via ID enumeration.

Apply scoping:

- const credential = await db
-   .select()
-   .from(sshCredentials)
-   .where(eq(sshCredentials.id, credentialId))
-   .limit(1);
+ const credential = await db
+   .select()
+   .from(sshCredentials)
+   .where(and(
+     eq(sshCredentials.id, credentialId),
+     eq(sshCredentials.userId, (req as any).userId)
+   ))
+   .limit(1);

- const targetHost = await db
-   .select()
-   .from(sshData)
-   .where(eq(sshData.id, targetHostId))
-   .limit(1);
+ const targetHost = await db
+   .select()
+   .from(sshData)
+   .where(and(
+     eq(sshData.id, targetHostId),
+     eq(sshData.userId, (req as any).userId)
+   ))
+   .limit(1);

Also validate that any resolved hostCredential (when host uses credential) belongs to the same user.

Also applies to: 1317-1363, 1364-1396

_⚠️ Potential issue_ **Authorization bypass: deploy-to-host lacks user scoping for both credential and host. Blocker.** Endpoint does not restrict credential/host to the authenticated user. An attacker could deploy keys to another user’s hosts via ID enumeration. Apply scoping: ```diff - const credential = await db - .select() - .from(sshCredentials) - .where(eq(sshCredentials.id, credentialId)) - .limit(1); + const credential = await db + .select() + .from(sshCredentials) + .where(and( + eq(sshCredentials.id, credentialId), + eq(sshCredentials.userId, (req as any).userId) + )) + .limit(1); - const targetHost = await db - .select() - .from(sshData) - .where(eq(sshData.id, targetHostId)) - .limit(1); + const targetHost = await db + .select() + .from(sshData) + .where(and( + eq(sshData.id, targetHostId), + eq(sshData.userId, (req as any).userId) + )) + .limit(1); ``` Also validate that any resolved hostCredential (when host uses credential) belongs to the same user. Also applies to: 1317-1363, 1364-1396 <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +245,4 @@
console.log('First 100 chars:', privateKeyData?.substring(0, 100) || 'undefined');
console.log('ssh2Utils available:', typeof ssh2Utils);
console.log('parseKey function available:', typeof ssh2Utils?.parseKey);
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

⚠️ Potential issue

Do not log key material (even partial).

Printing the first 100 chars of private/public keys (and derived key data) is a severe secret‑leak risk.

Apply this sanitization:

-  console.log('=== SSH Key Parsing Debug ===');
-  console.log('Key length:', privateKeyData?.length || 'undefined');
-  console.log('First 100 chars:', privateKeyData?.substring(0, 100) || 'undefined');
-  console.log('ssh2Utils available:', typeof ssh2Utils);
-  console.log('parseKey function available:', typeof ssh2Utils?.parseKey);
+  // debug disabled; avoid logging sensitive key material

... (public key parser)
-  console.log('=== SSH Public Key Parsing Debug ===');
-  console.log('Public key length:', publicKeyData?.length || 'undefined');
-  console.log('First 100 chars:', publicKeyData?.substring(0, 100) || 'undefined');
+  // debug disabled; avoid logging sensitive key material

... (validation)
-  console.log('=== Key Pair Validation Debug ===');
-  console.log('Private key length:', privateKeyData?.length || 'undefined');
-  console.log('Public key length:', publicKeyData?.length || 'undefined');
+  // debug disabled; avoid logging sensitive key material

-        console.log('Generated key data:', generatedKeyData.substring(0, 50) + '...');
-        console.log('Provided key data:', providedKeyData.substring(0, 50) + '...');
+        // do not log key data

If you need diagnostics, gate redacted logs behind a dedicated DEBUG flag and never print raw key content.

Also applies to: 369-372, 431-434, 488-490

🤖 Prompt for AI Agents
In src/backend/utils/ssh-key-utils.ts around lines 243-248 (and also address
similar logging at 369-372, 431-434, 488-490): the current debug prints reveal
private/public key material (first 100 chars) which must be removed; replace
those console.log statements so they never output raw key bytes—either remove
them or gate them behind a dedicated DEBUG flag and only emit non-secret
diagnostics such as key length, presence booleans, or a redacted/hashing
indicator (e.g., show length or a fixed string like "<REDACTED>" or a truncated
hash), and ensure any gated logging explicitly excludes any substring of the key
itself and that the DEBUG flag is checked before evaluating any key-derived
values.
_⚠️ Potential issue_ **Do not log key material (even partial).** Printing the first 100 chars of private/public keys (and derived key data) is a severe secret‑leak risk. Apply this sanitization: ```diff - console.log('=== SSH Key Parsing Debug ==='); - console.log('Key length:', privateKeyData?.length || 'undefined'); - console.log('First 100 chars:', privateKeyData?.substring(0, 100) || 'undefined'); - console.log('ssh2Utils available:', typeof ssh2Utils); - console.log('parseKey function available:', typeof ssh2Utils?.parseKey); + // debug disabled; avoid logging sensitive key material ... (public key parser) - console.log('=== SSH Public Key Parsing Debug ==='); - console.log('Public key length:', publicKeyData?.length || 'undefined'); - console.log('First 100 chars:', publicKeyData?.substring(0, 100) || 'undefined'); + // debug disabled; avoid logging sensitive key material ... (validation) - console.log('=== Key Pair Validation Debug ==='); - console.log('Private key length:', privateKeyData?.length || 'undefined'); - console.log('Public key length:', publicKeyData?.length || 'undefined'); + // debug disabled; avoid logging sensitive key material - console.log('Generated key data:', generatedKeyData.substring(0, 50) + '...'); - console.log('Provided key data:', providedKeyData.substring(0, 50) + '...'); + // do not log key data ``` If you need diagnostics, gate redacted logs behind a dedicated DEBUG flag and never print raw key content. Also applies to: 369-372, 431-434, 488-490 <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/utils/ssh-key-utils.ts around lines 243-248 (and also address similar logging at 369-372, 431-434, 488-490): the current debug prints reveal private/public key material (first 100 chars) which must be removed; replace those console.log statements so they never output raw key bytes—either remove them or gate them behind a dedicated DEBUG flag and only emit non-secret diagnostics such as key length, presence booleans, or a redacted/hashing indicator (e.g., show length or a fixed string like "<REDACTED>" or a truncated hash), and ensure any gated logging explicitly excludes any substring of the key itself and that the DEBUG flag is checked before evaluating any key-derived values. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +288,4 @@
} else {
publicKey = `${keyType} ${base64Data}`;
}
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

⚠️ Potential issue

Bug: Misencoding public key — getPublicSSH() already returns OpenSSH text.

You’re base64‑encoding the OpenSSH public key again, producing invalid keys.

Apply this fix:

-              const base64Data = publicKeyBuffer.toString('base64');
-
-              // Create proper SSH public key format: "keytype base64data"
-              if (keyType === 'ssh-rsa') {
-                publicKey = `ssh-rsa ${base64Data}`;
-              } else if (keyType === 'ssh-ed25519') {
-                publicKey = `ssh-ed25519 ${base64Data}`;
-              } else if (keyType.startsWith('ecdsa-')) {
-                publicKey = `${keyType} ${base64Data}`;
-              } else {
-                publicKey = `${keyType} ${base64Data}`;
-              }
+              // ssh2 returns the OpenSSH public key as an ASCII buffer: "type base64data"
+              publicKey = publicKeyBuffer.toString('ascii').trim();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              // ssh2 returns the OpenSSH public key as an ASCII buffer: "type base64data"
              publicKey = publicKeyBuffer.toString('ascii').trim();
🤖 Prompt for AI Agents
In src/backend/utils/ssh-key-utils.ts around lines 278-291, the code is
base64-encoding an OpenSSH public key buffer and re-prepending key type which
produces invalid keys; instead, use the buffer's UTF-8 text directly (e.g.
publicKey = publicKeyBuffer.toString('utf8').trim()) and remove the conditional
keyType-based re-encoding/concatenation so the already-formatted OpenSSH public
key returned by getPublicSSH() is used as-is.
_⚠️ Potential issue_ **Bug: Misencoding public key — `getPublicSSH()` already returns OpenSSH text.** You’re base64‑encoding the OpenSSH public key again, producing invalid keys. Apply this fix: ```diff - const base64Data = publicKeyBuffer.toString('base64'); - - // Create proper SSH public key format: "keytype base64data" - if (keyType === 'ssh-rsa') { - publicKey = `ssh-rsa ${base64Data}`; - } else if (keyType === 'ssh-ed25519') { - publicKey = `ssh-ed25519 ${base64Data}`; - } else if (keyType.startsWith('ecdsa-')) { - publicKey = `${keyType} ${base64Data}`; - } else { - publicKey = `${keyType} ${base64Data}`; - } + // ssh2 returns the OpenSSH public key as an ASCII buffer: "type base64data" + publicKey = publicKeyBuffer.toString('ascii').trim(); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion // ssh2 returns the OpenSSH public key as an ASCII buffer: "type base64data" publicKey = publicKeyBuffer.toString('ascii').trim(); ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/utils/ssh-key-utils.ts around lines 278-291, the code is base64-encoding an OpenSSH public key buffer and re-prepending key type which produces invalid keys; instead, use the buffer's UTF-8 text directly (e.g. publicKey = publicKeyBuffer.toString('utf8').trim()) and remove the conditional keyType-based re-encoding/concatenation so the already-formatted OpenSSH public key returned by getPublicSSH() is used as-is. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +333,4 @@
success: keyType !== 'unknown'
};
} catch (error) {
console.error('Exception during SSH key parsing:', error);
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

⚠️ Potential issue

Don’t mark fallback heuristics as success.

If ssh2 parsing fails, heuristics should not set success: true. Return success: false unless we positively parsed and/or generated a public key.

-      publicKey = '';
-      if (keyType !== 'unknown') {
-        console.log(`SSH key type detected successfully with fallback: ${keyType}`);
-      }
+      publicKey = '';
-      success: keyType !== 'unknown'
+      success: useSSH2
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      publicKey = '';
    }

    return {
      privateKey: privateKeyData,
      publicKey,
      keyType,
      success: useSSH2
    };
  } catch (error) {
    console.error('Exception during SSH key parsing:', error);
🤖 Prompt for AI Agents
In src/backend/utils/ssh-key-utils.ts around lines 324–336, the current fallback
heuristics can set success: true when ssh2 parsing failed; change this so
success is only true when we positively parsed (or generated) a public key.
Implement a boolean (e.g., parsedWithParser) that is set only when the primary
ssh2 parser succeeds, and use success: parsedWithParser && Boolean(publicKey)
(or, if you generate a public key from a private key, set parsedWithParser when
generation succeeds). Ensure fallback detection still sets keyType and logs but
does not flip success to true.
_⚠️ Potential issue_ **Don’t mark fallback heuristics as success.** If ssh2 parsing fails, heuristics should not set `success: true`. Return `success: false` unless we positively parsed and/or generated a public key. ```diff - publicKey = ''; - if (keyType !== 'unknown') { - console.log(`SSH key type detected successfully with fallback: ${keyType}`); - } + publicKey = ''; ``` ```diff - success: keyType !== 'unknown' + success: useSSH2 ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion publicKey = ''; } return { privateKey: privateKeyData, publicKey, keyType, success: useSSH2 }; } catch (error) { console.error('Exception during SSH key parsing:', error); ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/utils/ssh-key-utils.ts around lines 324–336, the current fallback heuristics can set success: true when ssh2 parsing failed; change this so success is only true when we positively parsed (or generated) a public key. Implement a boolean (e.g., parsedWithParser) that is set only when the primary ssh2 parser succeeds, and use success: parsedWithParser && Boolean(publicKey) (or, if you generate a public key from a private key, set parsedWithParser when generation succeeds). Ensure fallback detection still sets keyType and logs but does not flip success to true. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +513,4 @@
privateKeyType: privateKeyInfo.keyType,
publicKeyType: publicKeyInfo.keyType,
error: 'Unable to verify key pair match, but key types are compatible'
};
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

⚠️ Potential issue

Validation must be definitive — don’t return true without a match.

Currently returns isValid: true when types match but key matching is unverified. That can accept mismatched pairs.

-    return {
-      isValid: true, // Assume valid if types match and no errors
-      privateKeyType: privateKeyInfo.keyType,
-      publicKeyType: publicKeyInfo.keyType,
-      error: 'Unable to verify key pair match, but key types are compatible'
-    };
+    return {
+      isValid: false,
+      privateKeyType: privateKeyInfo.keyType,
+      publicKeyType: publicKeyInfo.keyType,
+      error: 'Unable to verify key pair match'
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // If we can't generate public key or compare, just check if types match
    return {
      isValid: false,
      privateKeyType: privateKeyInfo.keyType,
      publicKeyType: publicKeyInfo.keyType,
      error: 'Unable to verify key pair match'
    };
🤖 Prompt for AI Agents
In src/backend/utils/ssh-key-utils.ts around lines 510 to 516, the function
currently returns isValid: true when public/private key matching couldn't be
verified but key types match; change this to return isValid: false in that
fallback case so the validation is definitive, update the error message to state
verification was not possible and that the pair should not be trusted (e.g.,
"Unable to verify key pair match — treat as invalid"), and ensure privateKeyType
and publicKeyType are still returned for debugging; do not assume validity when
matching could not be confirmed.
_⚠️ Potential issue_ **Validation must be definitive — don’t return true without a match.** Currently returns `isValid: true` when types match but key matching is unverified. That can accept mismatched pairs. ```diff - return { - isValid: true, // Assume valid if types match and no errors - privateKeyType: privateKeyInfo.keyType, - publicKeyType: publicKeyInfo.keyType, - error: 'Unable to verify key pair match, but key types are compatible' - }; + return { + isValid: false, + privateKeyType: privateKeyInfo.keyType, + publicKeyType: publicKeyInfo.keyType, + error: 'Unable to verify key pair match' + }; ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion // If we can't generate public key or compare, just check if types match return { isValid: false, privateKeyType: privateKeyInfo.keyType, publicKeyType: publicKeyInfo.keyType, error: 'Unable to verify key pair match' }; ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/backend/utils/ssh-key-utils.ts around lines 510 to 516, the function currently returns isValid: true when public/private key matching couldn't be verified but key types match; change this to return isValid: false in that fallback case so the validation is definitive, update the error message to state verification was not possible and that the pair should not be trusted (e.g., "Unable to verify key pair match — treat as invalid"), and ensure privateKeyType and publicKeyType are still returned for debugging; do not assume validity when matching could not be confirmed. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -206,0 +314,4 @@
};
return keyTypeMap[keyType] || keyType;
};
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

🛠️ Refactor suggestion

Key type detection should react to keyPassword changes.

Trigger detection when keyPassword changes if a key is present.

useEffect(() => {
  const keyVal = form.watch("key");
  const pass = form.watch("keyPassword");
  if (typeof keyVal === "string" && keyVal.trim()) {
    debouncedKeyDetection(keyVal, pass);
  }
-}, []);
+}, [form.watch("keyPassword")]);

Note: capture values via form.getValues() to avoid extra renders.

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

🤖 Prompt for AI Agents
In src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx around lines 239 to 254,
key type detection only runs when the key value changes and does not react to
changes to keyPassword; update the component to also trigger detection when
keyPassword changes (but only if a key is present) by reading current values
with form.getValues() inside the effect/handler to avoid extra renders, call
detectKeyType with the latest key and password, and keep the existing
success/error handling and loading state management.
_🛠️ Refactor suggestion_ **Key type detection should react to keyPassword changes.** Trigger detection when keyPassword changes if a key is present. ```diff useEffect(() => { const keyVal = form.watch("key"); const pass = form.watch("keyPassword"); if (typeof keyVal === "string" && keyVal.trim()) { debouncedKeyDetection(keyVal, pass); } -}, []); +}, [form.watch("keyPassword")]); ``` Note: capture values via form.getValues() to avoid extra renders. > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/ui/Desktop/Apps/Credentials/CredentialEditor.tsx around lines 239 to 254, key type detection only runs when the key value changes and does not react to changes to keyPassword; update the component to also trigger detection when keyPassword changes (but only if a key is present) by reading current values with form.getValues() inside the effect/handler to avoid extra renders, call detectKeyType with the latest key and password, and keep the existing success/error handling and loading state management. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -93,0 +130,4 @@
toast.error("Only SSH key-based credentials can be deployed");
return;
}
if (!credential.publicKey) {
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

🛠️ Refactor suggestion

Type safety for hosts; avoid any[].

Use SSHHost[] for availableHosts and number for selectedHostId where possible. This reduces parseInt churn and runtime errors.

- const [availableHosts, setAvailableHosts] = useState<any[]>([]);
- const [selectedHostId, setSelectedHostId] = useState<string>("");
+ const [availableHosts, setAvailableHosts] = useState<SSHHost[]>([]);
+ const [selectedHostId, setSelectedHostId] = useState<string>("");

And when calling:

- parseInt(selectedHostId)
+ Number(selectedHostId)

Also applies to: 100-107

🤖 Prompt for AI Agents
In src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx around lines 88 to 93
(also applies to lines 100-107), the state uses any[] and string for
host-related values causing extra parseInt usage and weak typing; change
availableHosts to SSHHost[] and selectedHostId to number (initialize
selectedHostId as 0 or -1 as sentinel), update setAvailableHosts usages to
push/fetch SSHHost objects, remove parseInt calls by ensuring host IDs are
numeric where provided, and update any event handlers, dropdown/select value
props, comparisons, and API call params to accept and use number IDs instead of
strings so type-safety is preserved across the component.
_🛠️ Refactor suggestion_ **Type safety for hosts; avoid any[].** Use SSHHost[] for availableHosts and number for selectedHostId where possible. This reduces parseInt churn and runtime errors. ```diff - const [availableHosts, setAvailableHosts] = useState<any[]>([]); - const [selectedHostId, setSelectedHostId] = useState<string>(""); + const [availableHosts, setAvailableHosts] = useState<SSHHost[]>([]); + const [selectedHostId, setSelectedHostId] = useState<string>(""); ``` And when calling: ```diff - parseInt(selectedHostId) + Number(selectedHostId) ``` Also applies to: 100-107 <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/ui/Desktop/Apps/Credentials/CredentialsManager.tsx around lines 88 to 93 (also applies to lines 100-107), the state uses any[] and string for host-related values causing extra parseInt usage and weak typing; change availableHosts to SSHHost[] and selectedHostId to number (initialize selectedHostId as 0 or -1 as sentinel), update setAvailableHosts usages to push/fetch SSHHost objects, remove parseInt calls by ensuring host IDs are numeric where provided, and update any event handlers, dropdown/select value props, comparisons, and API call params to accept and use number IDs instead of strings so type-safety is preserved across the component. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

🛠️ Refactor suggestion

Add response types for new credential/key APIs.

Avoid Promise; define small interfaces (e.g., { success: boolean; keyType?: string; publicKey?: string; error?: string }).

_🛠️ Refactor suggestion_ **Add response types for new credential/key APIs.** Avoid Promise<any>; define small interfaces (e.g., { success: boolean; keyType?: string; publicKey?: string; error?: string }). <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +88,4 @@
: 'text-green-600'
}`}>
{getFriendlyKeyTypeName(detectedKeyType)}
</span>
coderabbitai[bot] (Migrated from github.com) commented 2025-09-14 23:13:15 +00:00

🛠️ Refactor suggestion

Avoid importing backend-only utils in the frontend; inline or move key-type map to a shared UI module.

getFriendlyKeyTypeName lives under backend/utils; bundlers may pull server-only deps. Create a small UI helper or shared constants and import from there.

Proposed UI helper:

export const friendlyKeyType = (t: string) =>
  ({'ssh-rsa':'RSA','ssh-ed25519':'Ed25519','ecdsa-sha2-nistp256':'ECDSA P-256','ecdsa-sha2-nistp384':'ECDSA P-384','ecdsa-sha2-nistp521':'ECDSA P-521','ssh-dss':'DSA','rsa-sha2-256':'RSA-SHA2-256','rsa-sha2-512':'RSA-SHA2-512','invalid':'Invalid','error':'Error','unknown':'Unknown'} as const)[t] ?? t;

Also applies to: 205-210

🤖 Prompt for AI Agents
In unified_key_section.tsx around lines 85-91 (and similarly at 205-210) you are
importing a backend-only helper getFriendlyKeyTypeName which can pull
server-only deps into the frontend; replace that usage by creating a small
UI/shared helper (e.g., a friendlyKeyType map/function in a ui/helpers or
shared/constants module) that maps key type strings to display names, export it
with proper typings, import that helper into unified_key_section.tsx, and update
the JSX to call the new helper instead of the backend util so the frontend
bundle has no server-only dependency.
_🛠️ Refactor suggestion_ **Avoid importing backend-only utils in the frontend; inline or move key-type map to a shared UI module.** getFriendlyKeyTypeName lives under backend/utils; bundlers may pull server-only deps. Create a small UI helper or shared constants and import from there. Proposed UI helper: ```ts export const friendlyKeyType = (t: string) => ({'ssh-rsa':'RSA','ssh-ed25519':'Ed25519','ecdsa-sha2-nistp256':'ECDSA P-256','ecdsa-sha2-nistp384':'ECDSA P-384','ecdsa-sha2-nistp521':'ECDSA P-521','ssh-dss':'DSA','rsa-sha2-256':'RSA-SHA2-256','rsa-sha2-512':'RSA-SHA2-512','invalid':'Invalid','error':'Error','unknown':'Unknown'} as const)[t] ?? t; ``` Also applies to: 205-210 <details> <summary>🤖 Prompt for AI Agents</summary> ``` In unified_key_section.tsx around lines 85-91 (and similarly at 205-210) you are importing a backend-only helper getFriendlyKeyTypeName which can pull server-only deps into the frontend; replace that usage by creating a small UI/shared helper (e.g., a friendlyKeyType map/function in a ui/helpers or shared/constants module) that maps key type strings to display names, export it with proper typings, import that helper into unified_key_section.tsx, and update the JSX to call the new helper instead of the backend util so the frontend bundle has no server-only dependency. ``` </details> <!-- fingerprinting:phantom:medusa:chinchilla --> <!-- This is an auto-generated comment by CodeRabbit -->
Sign in to join this conversation.