fix: portable 绿色单机版 SSH 连接 + 主机数据 + 打包体积
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
SSH 连接修复: - main.cjs 顶部在所有 ssh2 require 之前安装 boringSslDhCompat 补丁 - 5 条 SSH 连接路径统一 legacyAlgorithms 默认值为 true portable 版主机数据修复: - main.cjs portable 模式设置 NETCATTY_PORTABLE=1 - preload.cjs 暴露 window.netcatty.isPortable - useVaultState portable 模式跳过 safeStorage DPAPI 加密的 hosts_v1 - inspection saveVaultHosts handler 补 legacyAlgorithms/tags/os 字段映射 打包体积修复: - 恢复 electron-builder.config.cjs 原始 40+ 条排除规则 - package.json 加 pend 运行时依赖
This commit is contained in:
@@ -1442,7 +1442,13 @@ export const useVaultState = () => {
|
|||||||
recoverPluginImporterTransaction(localStorageAdapter, PLUGIN_IMPORT_TRANSACTION_KEYS);
|
recoverPluginImporterTransaction(localStorageAdapter, PLUGIN_IMPORT_TRANSACTION_KEYS);
|
||||||
});
|
});
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const savedHosts = localStorageAdapter.read<Host[]>(STORAGE_KEY_HOSTS);
|
// portable 版 safeStorage DPAPI 绑定 userData 路径,解不开 dev 复制过来的
|
||||||
|
// LS 加密 blob。强制跳过 hosts_v1,直接走 else 分支从 inspection-data.json
|
||||||
|
// 读明文 _vault_hosts(含 password + legacyAlgorithms)。
|
||||||
|
const isPortable = (window as any).netcatty?.isPortable === true;
|
||||||
|
const savedHosts = isPortable
|
||||||
|
? undefined
|
||||||
|
: localStorageAdapter.read<Host[]>(STORAGE_KEY_HOSTS);
|
||||||
|
|
||||||
if (savedHosts) {
|
if (savedHosts) {
|
||||||
// Capture version before the async gap so that any write occurring
|
// Capture version before the async gap so that any write occurring
|
||||||
@@ -1452,6 +1458,17 @@ export const useVaultState = () => {
|
|||||||
const decrypted = await decryptHosts(savedHosts);
|
const decrypted = await decryptHosts(savedHosts);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (ver === hostsWriteVersion.current) {
|
if (ver === hostsWriteVersion.current) {
|
||||||
|
// safeStorage 解密失败时 decryptField 会返回 enc:v1 占位符,
|
||||||
|
// sanitizeHost 再把它过滤成 undefined → 密码/私钥消失。
|
||||||
|
// 检查解密后是否大部分凭证都被清空了,是的话强制 fallback
|
||||||
|
// 到 inspectionStorage 的明文 _vault_hosts(打包版 portable 场景)。
|
||||||
|
const hasAnyPassword = decrypted.some((h: Host) =>
|
||||||
|
h.password && !h.password.startsWith("enc:v1:"));
|
||||||
|
const hasAnyPrivateKey = decrypted.some((h: Host) =>
|
||||||
|
h.privateKey && !h.privateKey.startsWith("enc:v1:"));
|
||||||
|
const credentialsLikelyLost =
|
||||||
|
decrypted.length > 0 && !hasAnyPassword && !hasAnyPrivateKey;
|
||||||
|
|
||||||
const sanitized = normalizeVaultOrder(
|
const sanitized = normalizeVaultOrder(
|
||||||
migrateHostsFromLegacyLineTimestamps(
|
migrateHostsFromLegacyLineTimestamps(
|
||||||
decrypted.map((host) => sanitizeHost(host)),
|
decrypted.map((host) => sanitizeHost(host)),
|
||||||
@@ -1473,6 +1490,41 @@ export const useVaultState = () => {
|
|||||||
return commitEncryptedHostsUnderVaultLock(ver, sanitized, enc);
|
return commitEncryptedHostsUnderVaultLock(ver, sanitized, enc);
|
||||||
});
|
});
|
||||||
hostsWritePendingRef.current = writePromise;
|
hostsWritePendingRef.current = writePromise;
|
||||||
|
|
||||||
|
// 解密后凭证丢失 → 用 inspectionStorage 的明文覆盖
|
||||||
|
if (credentialsLikelyLost) {
|
||||||
|
const nc = (window as any).netcatty;
|
||||||
|
if (nc?.inspection?.getVaultHosts) {
|
||||||
|
try {
|
||||||
|
const vaultHosts = await nc.inspection.getVaultHosts();
|
||||||
|
if (Array.isArray(vaultHosts) && vaultHosts.length > 0) {
|
||||||
|
const fallback: Host[] = vaultHosts.map((v: any) => ({
|
||||||
|
id: v.id,
|
||||||
|
label: v.label || "",
|
||||||
|
hostname: v.hostname || "",
|
||||||
|
port: v.port || 22,
|
||||||
|
username: v.username || "",
|
||||||
|
password: v.password || undefined,
|
||||||
|
identityFileId: v.identityFileId || undefined,
|
||||||
|
privateKey: v.privateKey || "",
|
||||||
|
passphrase: v.passphrase || "",
|
||||||
|
tags: v.tags || [],
|
||||||
|
os: v.os || "linux",
|
||||||
|
type: v.type || "",
|
||||||
|
distro: v.distro || "",
|
||||||
|
group: v.group || "",
|
||||||
|
legacyAlgorithms: v.legacyAlgorithms,
|
||||||
|
}));
|
||||||
|
const fbSanitized = normalizeVaultOrder(
|
||||||
|
fallback.map((h) => sanitizeHost(h)),
|
||||||
|
);
|
||||||
|
if (cancelled) return;
|
||||||
|
hostsRef.current = fbSanitized;
|
||||||
|
setHosts(fbSanitized);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 本地 Local Storage 为空(可能是 origin 隔离导致的打包版环境)
|
// 本地 Local Storage 为空(可能是 origin 隔离导致的打包版环境)
|
||||||
@@ -1499,6 +1551,7 @@ export const useVaultState = () => {
|
|||||||
type: v.type || "",
|
type: v.type || "",
|
||||||
distro: v.distro || "",
|
distro: v.distro || "",
|
||||||
group: v.group || "",
|
group: v.group || "",
|
||||||
|
legacyAlgorithms: v.legacyAlgorithms,
|
||||||
}));
|
}));
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const sanitized = normalizeVaultOrder(
|
const sanitized = normalizeVaultOrder(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* 运行监控 - 3D 拓扑大屏
|
* 运行监控 - 3D 拓扑大屏
|
||||||
*
|
*
|
||||||
* 移植自 ITYUNWEI TopologyView.vue
|
* 移植自 ITYUNWEI TopologyView.vue
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { moshExtraResources } = require('./scripts/mosh-extra-resources.cjs');
|
const { moshExtraResources } = require('./scripts/mosh-extra-resources.cjs');
|
||||||
const { etExtraResources } = require('./scripts/et-extra-resources.cjs');
|
const { etExtraResources } = require('./scripts/et-extra-resources.cjs');
|
||||||
// 说明:electron-builder 依赖收集器在 monorepo/workspace 项目里会漏掉
|
|
||||||
// workspace 子包引用但根 package.json 没声明的运行时依赖(hoist 上来的),
|
|
||||||
// 且追踪传递依赖时容易漏包。这里直接把整个 node_modules 目录纳入 files,
|
|
||||||
// 确保运行时 require 什么都能找到。用排除列表去掉前端/构建工具/文档/测试代码。
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('electron-builder').Configuration}
|
* @type {import('electron-builder').Configuration}
|
||||||
@@ -58,19 +54,117 @@ module.exports = {
|
|||||||
output: 'release'
|
output: 'release'
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
// electron-builder 收集器只读根 package.json,但 workspace 子包的运行时依赖
|
'dist/**/*',
|
||||||
// 会 hoist 到根 node_modules 却没在根 package.json 里声明。
|
'electron/**/*',
|
||||||
// 收集器会漏掉这些包,所以把漏的补进根 package.json 后它就能追踪完整传递依赖树。
|
// The Windows Hello helper is shipped once through win.extraResources.
|
||||||
"dist/**/*",
|
// Exclude local per-arch build output from app.asar on every platform.
|
||||||
"electron/**/*",
|
'!electron/bridges/windowsHelloHelper/build/**/*',
|
||||||
"!electron/bridges/windowsHelloHelper/build/**/*",
|
// Runtime smoke fixtures are built test packages, not host resources.
|
||||||
"!electron/plugins/fixtures/**/*",
|
// Keep them out of production ASARs so no example plugin can be
|
||||||
"infrastructure/config/terminalFlowConstants.cjs",
|
// mistaken for an installed or host-trusted package.
|
||||||
"infrastructure/config/terminalFlowConstants.json",
|
'!electron/plugins/fixtures/**/*',
|
||||||
"domain/terminalPromptSecurity.shared.cjs",
|
// Main-process terminal flow control reads shared thresholds from here
|
||||||
"lib/**/*.cjs",
|
// (terminalFlowAck.cjs). Must ship beside electron/ in app.asar.
|
||||||
"lib/**/*.json",
|
'infrastructure/config/terminalFlowConstants.cjs',
|
||||||
"skills/**/*"
|
'infrastructure/config/terminalFlowConstants.json',
|
||||||
|
// Renderer and Mosh's main-process bootstrap share the same
|
||||||
|
// fail-closed prompt classifier at runtime.
|
||||||
|
'domain/terminalPromptSecurity.shared.cjs',
|
||||||
|
'lib/**/*.cjs',
|
||||||
|
'lib/**/*.json',
|
||||||
|
'skills/**/*',
|
||||||
|
'!public/**/*',
|
||||||
|
'!**/*.map',
|
||||||
|
'!**/*.d.ts',
|
||||||
|
'!**/*.d.mts',
|
||||||
|
'!**/*.d.cts',
|
||||||
|
'!**/*.ts',
|
||||||
|
'!**/*.tsx',
|
||||||
|
'!**/*.test.*',
|
||||||
|
'!**/*.spec.*',
|
||||||
|
'!**/__tests__/**/*',
|
||||||
|
'!**/test/**/*',
|
||||||
|
'!**/tests/**/*',
|
||||||
|
'!**/example/**/*',
|
||||||
|
'!**/examples/**/*',
|
||||||
|
'!node_modules/**/docs/**/*',
|
||||||
|
'!node_modules/**/doc/**/*',
|
||||||
|
'!node_modules/**/benchmark/**/*',
|
||||||
|
'!node_modules/**/benchmarks/**/*',
|
||||||
|
// Renderer-only packages are compiled into dist by Vite. Keep them
|
||||||
|
// installed for npm run dev/build, but do not ship the duplicate source
|
||||||
|
// packages in release artifacts.
|
||||||
|
'!node_modules/@fontsource/**/*',
|
||||||
|
'!node_modules/@monaco-editor/**/*',
|
||||||
|
'!node_modules/@radix-ui/**/*',
|
||||||
|
'!node_modules/@xterm/**/*',
|
||||||
|
'!node_modules/lucide-react/**/*',
|
||||||
|
'!node_modules/monaco-editor/**/*',
|
||||||
|
'!node_modules/react/**/*',
|
||||||
|
'!node_modules/react-dom/**/*',
|
||||||
|
'!node_modules/ai/**/*',
|
||||||
|
'!node_modules/@ai-sdk/**/*',
|
||||||
|
'!node_modules/@mdxeditor/**/*',
|
||||||
|
'!node_modules/streamdown/**/*',
|
||||||
|
'!node_modules/@streamdown/**/*',
|
||||||
|
'!node_modules/@tanstack/react-virtual/**/*',
|
||||||
|
'!node_modules/pinyin-pro/**/*',
|
||||||
|
'!node_modules/re2js/**/*',
|
||||||
|
'!node_modules/@eslint-community/regexpp/**/*',
|
||||||
|
'!node_modules/clsx/**/*',
|
||||||
|
'!node_modules/tailwind-merge/**/*',
|
||||||
|
'!node_modules/use-stick-to-bottom/**/*',
|
||||||
|
'!node_modules/lexical/**/*',
|
||||||
|
'!node_modules/@lexical/**/*',
|
||||||
|
'!node_modules/@codemirror/**/*',
|
||||||
|
'!node_modules/katex/**/*',
|
||||||
|
'!node_modules/shiki/**/*',
|
||||||
|
'!node_modules/@shiki/**/*',
|
||||||
|
// Heavy cloud completion specs are intentionally not bundled. The main
|
||||||
|
// process filters the same prefixes so dev and packaged builds behave
|
||||||
|
// consistently.
|
||||||
|
'!node_modules/@withfig/autocomplete/build/aws.js',
|
||||||
|
'!node_modules/@withfig/autocomplete/build/aws/**/*',
|
||||||
|
'!node_modules/@withfig/autocomplete/build/gcloud.js',
|
||||||
|
'!node_modules/@withfig/autocomplete/build/gcloud/**/*',
|
||||||
|
'!node_modules/@withfig/autocomplete/build/az/**/*',
|
||||||
|
// Fig specs are already compiled JavaScript; TypeScript is only pulled
|
||||||
|
// in by Fig helper packages as build tooling and is not needed at app
|
||||||
|
// runtime.
|
||||||
|
'!node_modules/typescript/**/*',
|
||||||
|
// ── Exclude per-platform native agent binaries (~100s of MB each). ──
|
||||||
|
// Netcatty is "bring your own CLI": each SDK is pointed at the user's
|
||||||
|
// system-installed CLI via an absolute path override (claude
|
||||||
|
// pathToClaudeCodeExecutable / codex codexPathOverride / copilot cliPath).
|
||||||
|
// Only the SDKs' JS is bundled; the heavy per-arch binaries are dropped.
|
||||||
|
// NOTE: claude-agent-sdk vendors the `claude` binary as a NESTED package
|
||||||
|
// (claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-<arch>),
|
||||||
|
// so this exclusion must match at any depth (**/), not just top-level.
|
||||||
|
// The codex/copilot exclusions target only the per-arch binary packages
|
||||||
|
// (codex-<arch> / copilot-<arch>) — NOT @openai/codex-sdk / copilot-sdk,
|
||||||
|
// whose JS we DO bundle. @github/copilot is the full ~288MB CLI (with
|
||||||
|
// per-platform prebuilds); netcatty uses the user's copilot via cliPath,
|
||||||
|
// so it is excluded entirely.
|
||||||
|
'!**/@anthropic-ai/claude-agent-sdk-*/**/*',
|
||||||
|
'!node_modules/@anthropic-ai/claude-code-*/**/*',
|
||||||
|
'!node_modules/@openai/codex-{darwin,linux,linuxmusl,win32}-*/**/*',
|
||||||
|
'!node_modules/@github/copilot-{darwin,linux,linuxmusl,win32}-*/**/*',
|
||||||
|
'!node_modules/@github/copilot/**/*',
|
||||||
|
'!node_modules/opencode-{darwin,linux,linuxmusl,windows}-*/**/*',
|
||||||
|
'!node_modules/opencode-ai/**/*',
|
||||||
|
// CodeBuddy follows the same first-party integration model as the
|
||||||
|
// other coding agents: Netcatty discovers and passes the user's
|
||||||
|
// installed CLI path to the SDK. Keep the small SDK wrapper, but do not
|
||||||
|
// bundle the full CodeBuddy CLI payload (rg vendors + web UI).
|
||||||
|
'!node_modules/@tencent-ai/agent-sdk/cli/**/*',
|
||||||
|
// Netcatty loads Cursor SDK through ESM dynamic import, so the duplicate
|
||||||
|
// CommonJS build and type metadata are not needed at runtime.
|
||||||
|
'!node_modules/@cursor/sdk/dist/cjs/**/*',
|
||||||
|
'!node_modules/@cursor/sdk/dist/**/*.d.ts',
|
||||||
|
'!node_modules/@cursor/sdk/dist/**/*.d.ts.map',
|
||||||
|
// sqlite3 rebuilds a native module for Electron; its upstream source
|
||||||
|
// tarball is build-time payload only.
|
||||||
|
'!node_modules/sqlite3/deps/**/*'
|
||||||
],
|
],
|
||||||
asarUnpack: [
|
asarUnpack: [
|
||||||
'node_modules/node-pty/**/*',
|
'node_modules/node-pty/**/*',
|
||||||
|
|||||||
@@ -1620,9 +1620,12 @@ function registerInspectionHandlers(ipcMain, options) {
|
|||||||
identityFileId: h.identityFileId,
|
identityFileId: h.identityFileId,
|
||||||
privateKey: h.privateKey,
|
privateKey: h.privateKey,
|
||||||
passphrase: h.passphrase,
|
passphrase: h.passphrase,
|
||||||
|
tags: h.tags || [],
|
||||||
|
os: h.os || "linux",
|
||||||
type: h.type || "",
|
type: h.type || "",
|
||||||
distro: h.distro || h.catalogId || "",
|
distro: h.distro || h.catalogId || "",
|
||||||
group: h.group || "",
|
group: h.group || "",
|
||||||
|
legacyAlgorithms: h.legacyAlgorithms,
|
||||||
}));
|
}));
|
||||||
writeStorage("_vault_hosts", mapped);
|
writeStorage("_vault_hosts", mapped);
|
||||||
return { ok: true, count: mapped.length };
|
return { ok: true, count: mapped.length };
|
||||||
|
|||||||
@@ -1388,7 +1388,7 @@ async function startPortForward(event, payload) {
|
|||||||
keepaliveCountMax: keepalivePolicy.keepaliveCountMax,
|
keepaliveCountMax: keepalivePolicy.keepaliveCountMax,
|
||||||
// Enable keyboard-interactive authentication (required for 2FA/MFA)
|
// Enable keyboard-interactive authentication (required for 2FA/MFA)
|
||||||
tryKeyboard: true,
|
tryKeyboard: true,
|
||||||
algorithms: buildAlgorithms(legacyAlgorithms, { skipEcdsaHostKey, algorithmOverrides }),
|
algorithms: buildAlgorithms(legacyAlgorithms !== false, { skipEcdsaHostKey, algorithmOverrides }),
|
||||||
};
|
};
|
||||||
connectOpts.hostVerifier = hostKeyVerifier.createHostVerifier({
|
connectOpts.hostVerifier = hostKeyVerifier.createHostVerifier({
|
||||||
sender,
|
sender,
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ function createSshDiagnosticLogger(enabled) {
|
|||||||
|
|
||||||
function shouldLogSshDebugMessage(msg) {
|
function shouldLogSshDebugMessage(msg) {
|
||||||
if (typeof msg !== "string") return false;
|
if (typeof msg !== "string") return false;
|
||||||
return /auth|publickey|keyboard|handshake|kex|newkeys|dh gex/i.test(msg);
|
return /auth|publickey|keyboard|handshake|kex|newkeys|dh gex|Inbound|Outbound/i.test(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachSshDebugLogger(connectOpts, logger = log) {
|
function attachSshDebugLogger(connectOpts, logger = log) {
|
||||||
@@ -586,8 +586,14 @@ async function connectThroughChain(event, options, jumpHosts, targetHost, target
|
|||||||
// when the leaf's ECDSA skip is applied to it. Treat both as
|
// when the leaf's ECDSA skip is applied to it. Treat both as
|
||||||
// strictly per-host: an unset hop value uses the hop's default
|
// strictly per-host: an unset hop value uses the hop's default
|
||||||
// offer, not the leaf's narrower setting.
|
// offer, not the leaf's narrower setting.
|
||||||
|
// Default legacyAlgorithms to true so NetMesh can connect to older
|
||||||
|
// network devices (Huawei, H3C, Cisco, etc.) that only support
|
||||||
|
// legacy KEX algorithms like diffie-hellman-group1-sha1. Modern
|
||||||
|
// devices still negotiate the strongest mutually supported algorithm
|
||||||
|
// first — offering legacy algorithms alongside modern ones does not
|
||||||
|
// weaken security on capable servers.
|
||||||
algorithms: buildAlgorithms(
|
algorithms: buildAlgorithms(
|
||||||
jump.legacyAlgorithms ?? options.legacyAlgorithms,
|
(jump.legacyAlgorithms ?? options.legacyAlgorithms) !== false,
|
||||||
{
|
{
|
||||||
skipEcdsaHostKey: jump.skipEcdsaHostKey,
|
skipEcdsaHostKey: jump.skipEcdsaHostKey,
|
||||||
algorithmOverrides: jump.algorithmOverrides,
|
algorithmOverrides: jump.algorithmOverrides,
|
||||||
@@ -607,7 +613,7 @@ async function connectThroughChain(event, options, jumpHosts, targetHost, target
|
|||||||
logSshAlgorithms("Jump host", connOpts.algorithms, {
|
logSshAlgorithms("Jump host", connOpts.algorithms, {
|
||||||
hostname: jump.hostname,
|
hostname: jump.hostname,
|
||||||
port: jump.port || 22,
|
port: jump.port || 22,
|
||||||
legacyAlgorithms: !!(jump.legacyAlgorithms ?? options.legacyAlgorithms),
|
legacyAlgorithms: ((jump.legacyAlgorithms ?? options.legacyAlgorithms) !== false),
|
||||||
skipEcdsaHostKey: !!jump.skipEcdsaHostKey,
|
skipEcdsaHostKey: !!jump.skipEcdsaHostKey,
|
||||||
hasAlgorithmOverrides: !!jump.algorithmOverrides,
|
hasAlgorithmOverrides: !!jump.algorithmOverrides,
|
||||||
}, sshDiagnosticLogger);
|
}, sshDiagnosticLogger);
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function createExecCommandApi(ctx) {
|
|||||||
// KEX / cipher / host-key set as the interactive terminal. Without
|
// KEX / cipher / host-key set as the interactive terminal. Without
|
||||||
// this, a host that needs the ECDSA skip or legacy algorithms would
|
// this, a host that needs the ECDSA skip or legacy algorithms would
|
||||||
// connect in the terminal but still fail the same handshake here.
|
// connect in the terminal but still fail the same handshake here.
|
||||||
algorithms: buildAlgorithms(payload.legacyAlgorithms, {
|
algorithms: buildAlgorithms(payload.legacyAlgorithms !== false, {
|
||||||
skipEcdsaHostKey: payload.skipEcdsaHostKey,
|
skipEcdsaHostKey: payload.skipEcdsaHostKey,
|
||||||
algorithmOverrides: payload.algorithmOverrides,
|
algorithmOverrides: payload.algorithmOverrides,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ function createStartSessionApi(ctx) {
|
|||||||
authMethod: options.authMethod || undefined,
|
authMethod: options.authMethod || undefined,
|
||||||
knownHosts: Array.isArray(options.knownHosts) ? options.knownHosts : undefined,
|
knownHosts: Array.isArray(options.knownHosts) ? options.knownHosts : undefined,
|
||||||
verifyHostKeys: options.verifyHostKeys !== false,
|
verifyHostKeys: options.verifyHostKeys !== false,
|
||||||
legacyAlgorithms: !!options.legacyAlgorithms,
|
legacyAlgorithms: options.legacyAlgorithms !== false,
|
||||||
skipEcdsaHostKey: !!options.skipEcdsaHostKey,
|
skipEcdsaHostKey: !!options.skipEcdsaHostKey,
|
||||||
algorithmOverrides: options.algorithmOverrides,
|
algorithmOverrides: options.algorithmOverrides,
|
||||||
useSshAgent: options.useSshAgent,
|
useSshAgent: options.useSshAgent,
|
||||||
@@ -1470,7 +1470,11 @@ function createStartSessionApi(ctx) {
|
|||||||
keepaliveCountMax: keepalivePolicy.keepaliveCountMax,
|
keepaliveCountMax: keepalivePolicy.keepaliveCountMax,
|
||||||
// Enable keyboard-interactive authentication (required for 2FA/MFA)
|
// Enable keyboard-interactive authentication (required for 2FA/MFA)
|
||||||
tryKeyboard: true,
|
tryKeyboard: true,
|
||||||
algorithms: buildAlgorithms(options.legacyAlgorithms, {
|
// Default legacyAlgorithms to true so older network devices (Huawei,
|
||||||
|
// H3C, Cisco, etc.) that only support legacy KEX algorithms like
|
||||||
|
// diffie-hellman-group1-sha1 can still connect. Modern devices still
|
||||||
|
// negotiate the strongest mutually supported algorithm first.
|
||||||
|
algorithms: buildAlgorithms(options.legacyAlgorithms !== false, {
|
||||||
skipEcdsaHostKey: options.skipEcdsaHostKey,
|
skipEcdsaHostKey: options.skipEcdsaHostKey,
|
||||||
algorithmOverrides: options.algorithmOverrides,
|
algorithmOverrides: options.algorithmOverrides,
|
||||||
}),
|
}),
|
||||||
@@ -1479,7 +1483,7 @@ function createStartSessionApi(ctx) {
|
|||||||
logSshAlgorithms("Target host", connectOpts.algorithms, {
|
logSshAlgorithms("Target host", connectOpts.algorithms, {
|
||||||
hostname: options.hostname,
|
hostname: options.hostname,
|
||||||
port: options.port || 22,
|
port: options.port || 22,
|
||||||
legacyAlgorithms: !!options.legacyAlgorithms,
|
legacyAlgorithms: options.legacyAlgorithms !== false,
|
||||||
skipEcdsaHostKey: !!options.skipEcdsaHostKey,
|
skipEcdsaHostKey: !!options.skipEcdsaHostKey,
|
||||||
hasAlgorithmOverrides: !!options.algorithmOverrides,
|
hasAlgorithmOverrides: !!options.algorithmOverrides,
|
||||||
}, log);
|
}, log);
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ if (process.env.ELECTRON_RUN_AS_NODE) {
|
|||||||
delete process.env.ELECTRON_RUN_AS_NODE;
|
delete process.env.ELECTRON_RUN_AS_NODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoringSSL DH compatibility shim — MUST be installed before ANY ssh2 require.
|
||||||
|
// Multiple bridges (sshBridge, sftpBridge, hostKeyVerifier, privateKeyNormalizer,
|
||||||
|
// sshAuthHelper, portForwardingBridge, inspectionExec, systemSshAgent...) each require
|
||||||
|
// ssh2 at module load. ssh2 destructes crypto.createDiffieHellmanGroup at load time,
|
||||||
|
// so the monkey-patch has to run before even the first ssh2 module load.
|
||||||
|
// Installing here (top of main, before any bridge requires) guarantees ordering.
|
||||||
|
require("./bridges/boringSslDhCompat.cjs").installBoringSslDhCompat();
|
||||||
|
|
||||||
// Load crash log bridge early so process-level error handlers can use it
|
// Load crash log bridge early so process-level error handlers can use it
|
||||||
const crashLogBridge = require("./bridges/crashLogBridge.cjs");
|
const crashLogBridge = require("./bridges/crashLogBridge.cjs");
|
||||||
const {
|
const {
|
||||||
@@ -267,6 +275,9 @@ const { applyPortableDataDirectory } = require("./portableData.cjs");
|
|||||||
const portableData = applyPortableDataDirectory({ app });
|
const portableData = applyPortableDataDirectory({ app });
|
||||||
if (portableData) {
|
if (portableData) {
|
||||||
console.info(`[Main] Portable data directory: ${portableData.dataDirectory}`);
|
console.info(`[Main] Portable data directory: ${portableData.dataDirectory}`);
|
||||||
|
// portable 版 safeStorage DPAPI 绑定 userData 路径,解不开 dev 版 LS 加密 blob。
|
||||||
|
// 设置环境变量让 useVaultState 跳过 LS hosts_v1,直接从 inspection-data.json 读明文。
|
||||||
|
process.env.NETCATTY_PORTABLE = "1";
|
||||||
}
|
}
|
||||||
const appPath = path.join(__dirname, "..");
|
const appPath = path.join(__dirname, "..");
|
||||||
const preload = path.join(__dirname, "preload.cjs");
|
const preload = path.join(__dirname, "preload.cjs");
|
||||||
|
|||||||
@@ -945,7 +945,12 @@ function isTrustedRendererLocation(allowedOrigins) {
|
|||||||
|
|
||||||
const allowedOrigins = getAllowedRendererOrigins();
|
const allowedOrigins = getAllowedRendererOrigins();
|
||||||
if (isTrustedRendererLocation(allowedOrigins)) {
|
if (isTrustedRendererLocation(allowedOrigins)) {
|
||||||
contextBridge.exposeInMainWorld("netcatty", { ...existing, ...api, ...figSpecApi });
|
contextBridge.exposeInMainWorld("netcatty", {
|
||||||
|
...existing, ...api, ...figSpecApi,
|
||||||
|
// portable 版标志:renderer 据此跳过 LS hosts_v1 加密读取,
|
||||||
|
// 直接走 inspection-data.json 明文(DPAPI 绑定 userData 路径解不开 dev 加密 blob)。
|
||||||
|
isPortable: process.env.NETCATTY_PORTABLE === "1",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// If a window navigates to an untrusted origin, do NOT expose the bridge.
|
// If a window navigates to an untrusted origin, do NOT expose the bridge.
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
"micromark-extension-gfm-strikethrough": "^2.1.0",
|
"micromark-extension-gfm-strikethrough": "^2.1.0",
|
||||||
"monaco-editor": "^0.55.1",
|
"monaco-editor": "^0.55.1",
|
||||||
"node-pty": "^1.1.0",
|
"node-pty": "^1.1.0",
|
||||||
|
"pend": "^1.2.0",
|
||||||
"pinyin-pro": "^3.28.1",
|
"pinyin-pro": "^3.28.1",
|
||||||
"re2js": "2.8.6",
|
"re2js": "2.8.6",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user