mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-26 15:03:34 +08:00
feat: 项目代码逻辑与安全分析
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
This commit is contained in:
parent
f2b4356b8f
commit
55a879f118
@ -461,6 +461,8 @@ export const en = {
|
||||
'settings.providers.toolSearchEnabled': 'Enable Tool Search',
|
||||
'settings.providers.toolSearchDesc': 'Load MCP and deferred tools on demand to reduce initial tool schema tokens. Disable it for weak models or providers that reject tool references.',
|
||||
'settings.providers.toolSearchUnsupported': 'Only Anthropic Messages providers support Tool Search here. OpenAI proxy formats keep full tool schemas available.',
|
||||
'settings.providers.availableModels': 'Available models — click to select',
|
||||
'settings.providers.clickToSelectModel': 'Click to use this model as the main model',
|
||||
|
||||
// Settings > Permissions
|
||||
'settings.permissions.title': 'Permission Mode',
|
||||
|
||||
@ -463,6 +463,8 @@ export const jp: Record<TranslationKey, string> = {
|
||||
'settings.providers.toolSearchEnabled': 'Tool Search を有効にする',
|
||||
'settings.providers.toolSearchDesc': 'MCP と遅延ツールを必要に応じて読み込み、初回のツール schema トークンを減らします。弱いモデルや tool_reference を拒否するプロバイダーでは無効にできます。',
|
||||
'settings.providers.toolSearchUnsupported': 'ここでは Anthropic Messages 形式のプロバイダーのみ Tool Search をサポートします。OpenAI プロキシ形式では完全なツール schema を維持します。',
|
||||
'settings.providers.availableModels': '利用可能なモデル — クリックして選択',
|
||||
'settings.providers.clickToSelectModel': 'クリックしてこのモデルを使用',
|
||||
|
||||
// Settings > Permissions
|
||||
'settings.permissions.title': '権限モード',
|
||||
|
||||
@ -463,6 +463,8 @@ export const kr: Record<TranslationKey, string> = {
|
||||
'settings.providers.toolSearchEnabled': 'Tool Search 사용',
|
||||
'settings.providers.toolSearchDesc': 'MCP와 지연 도구를 필요할 때 로드해 초기 도구 schema 토큰을 줄입니다. 약한 모델이나 tool_reference를 거부하는 공급자에서는 끌 수 있습니다.',
|
||||
'settings.providers.toolSearchUnsupported': '여기서는 Anthropic Messages 형식 공급자만 Tool Search를 지원합니다. OpenAI 프록시 형식은 전체 도구 schema를 유지합니다.',
|
||||
'settings.providers.availableModels': '사용 가능한 모델 — 클릭하여 선택',
|
||||
'settings.providers.clickToSelectModel': '클릭하여 이 모델을 주 모델로 사용',
|
||||
|
||||
// Settings > Permissions
|
||||
'settings.permissions.title': '권한 모드',
|
||||
|
||||
@ -463,6 +463,8 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.providers.toolSearchEnabled': '啟用 Tool Search',
|
||||
'settings.providers.toolSearchDesc': '按需載入 MCP 和延遲工具,減少首輪工具 schema token。弱模型或不支援 tool_reference 的服務商可以關閉。',
|
||||
'settings.providers.toolSearchUnsupported': '目前僅 Anthropic Messages 格式支援 Tool Search;OpenAI 代理格式會保留完整工具 schema。',
|
||||
'settings.providers.availableModels': '可用模型 — 點擊選擇',
|
||||
'settings.providers.clickToSelectModel': '點擊使用此模型作為主模型',
|
||||
|
||||
// Settings > Permissions
|
||||
'settings.permissions.title': '許可權模式',
|
||||
|
||||
@ -463,6 +463,8 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.providers.toolSearchEnabled': '启用 Tool Search',
|
||||
'settings.providers.toolSearchDesc': '按需加载 MCP 和延迟工具,减少首轮工具 schema token。弱模型或不支持 tool_reference 的服务商可以关闭。',
|
||||
'settings.providers.toolSearchUnsupported': '当前仅 Anthropic Messages 格式支持 Tool Search;OpenAI 代理格式会保留完整工具 schema。',
|
||||
'settings.providers.availableModels': '可用模型 — 点击选择',
|
||||
'settings.providers.clickToSelectModel': '点击使用此模型作为主模型',
|
||||
|
||||
// Settings > Permissions
|
||||
'settings.permissions.title': '权限模式',
|
||||
|
||||
@ -1794,6 +1794,33 @@ function ProviderFormModal({ open, onClose, mode, provider, presets }: ProviderF
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Available models — auto-fill main model when a chip is clicked */}
|
||||
{testResult?.availableModels && testResult.availableModels.length > 0 && (
|
||||
<div>
|
||||
<label className="text-xs font-medium text-[var(--color-text-secondary)] mb-1.5 block">
|
||||
{t('settings.providers.availableModels') || 'Available models'}
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{testResult.availableModels.slice(0, 20).map((model) => (
|
||||
<button
|
||||
key={model}
|
||||
type="button"
|
||||
onClick={() => handleModelChange('main', model)}
|
||||
title={t('settings.providers.clickToSelectModel') || 'Click to select'}
|
||||
className="max-w-[200px] truncate rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-surface-container-low)] px-2 py-1 text-xs text-[var(--color-text-primary)] transition-colors hover:border-[var(--color-border-focus)] hover:bg-[var(--color-surface-hover)] focus:outline-none focus:shadow-[var(--shadow-focus-ring)]"
|
||||
>
|
||||
{model}
|
||||
</button>
|
||||
))}
|
||||
{testResult.availableModels.length > 20 && (
|
||||
<span className="px-2 py-1 text-xs text-[var(--color-text-tertiary)]">
|
||||
+{testResult.availableModels.length - 20} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Settings JSON — editable, shown for all presets including official */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-primary)] mb-2 block">{t('settings.providers.settingsJson')}</label>
|
||||
|
||||
@ -322,8 +322,12 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
},
|
||||
|
||||
setLocale: (locale) => {
|
||||
const prev = get().locale
|
||||
set({ locale })
|
||||
try { localStorage.setItem(LOCALE_STORAGE_KEY, locale) } catch { /* noop */ }
|
||||
settingsApi.updateUser({ locale }).catch(() => {
|
||||
set({ locale: prev })
|
||||
})
|
||||
},
|
||||
|
||||
setTheme: async (theme) => {
|
||||
|
||||
@ -21,7 +21,8 @@ import { sessionService } from './services/sessionService.js'
|
||||
import { conversationService } from './services/conversationService.js'
|
||||
import { OPENAI_CODEX_REDIRECT_PATH } from '../services/openaiAuth/client.js'
|
||||
import { ensureDesktopCliLauncherInstalled } from './services/desktopCliLauncherService.js'
|
||||
import { enableConfigs } from '../utils/config.js'
|
||||
import { enableConfigs, getGlobalConfig } from '../utils/config.js'
|
||||
import { setLocale } from '../utils/i18n/index.js'
|
||||
import { diagnosticsService } from './services/diagnosticsService.js'
|
||||
import { ensurePersistentStorageUpgraded } from './services/persistentStorageMigrations.js'
|
||||
import { handleStaticH5Request } from './staticH5.js'
|
||||
@ -126,6 +127,9 @@ function originFromUrl(value: string | null): string | null {
|
||||
|
||||
export function startServer(port = PORT, host = HOST) {
|
||||
enableConfigs()
|
||||
// Apply persisted locale from ~/.claude/settings.json (set by desktop UI language switcher)
|
||||
const { locale } = getGlobalConfig()
|
||||
if (locale) setLocale(locale)
|
||||
// Warm the synchronous disconnect-grace cache from managed settings so the
|
||||
// first client disconnect honors the configured value (issue #764).
|
||||
void refreshDisconnectGraceMs()
|
||||
|
||||
@ -9,7 +9,8 @@ import {
|
||||
} from '../../utils/bash/commands.js'
|
||||
import { tryParseShellCommand } from '../../utils/bash/shellQuote.js'
|
||||
import { getDirectoryForPath } from '../../utils/path.js'
|
||||
import { allWorkingDirectories } from '../../utils/permissions/filesystem.js'
|
||||
import { getOriginalCwd } from '../../bootstrap/state.js'
|
||||
import { allWorkingDirectories, checkSymlinkPathSafety } from '../../utils/permissions/filesystem.js'
|
||||
import type { PermissionResult } from '../../utils/permissions/PermissionResult.js'
|
||||
import { createReadRuleSuggestion } from '../../utils/permissions/PermissionUpdate.js'
|
||||
import type { PermissionUpdate } from '../../utils/permissions/PermissionUpdateSchema.js'
|
||||
@ -691,6 +692,23 @@ function validateCommandPaths(
|
||||
decisionReason,
|
||||
}
|
||||
}
|
||||
|
||||
// Symlink TOCTOU check for write/create operations:
|
||||
// Block commands where the resolved path crosses a symlink outside the session.
|
||||
// Read-only operations (ls, cat, grep, etc.) are exempt since they don't modify files.
|
||||
if (operationType === 'write' || operationType === 'create') {
|
||||
const symlinkResult = checkSymlinkPathSafety(resolvedPath, [getOriginalCwd()])
|
||||
if (!symlinkResult.safe) {
|
||||
return {
|
||||
behavior: 'deny',
|
||||
message: `'${command}' to '${resolvedPath}' is blocked: contains a symlink pointing outside the working directory.`,
|
||||
decisionReason: {
|
||||
type: 'safetyCheck',
|
||||
reason: symlinkResult.reason ?? 'Symlink points outside the session working directory',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All paths are valid - return passthrough
|
||||
@ -994,6 +1012,21 @@ function validateOutputRedirections(
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Symlink TOCTOU check: ensure the resolved path doesn't cross a symlink
|
||||
// that points outside the session working directory.
|
||||
// This is defense-in-depth on top of the normal permission flow.
|
||||
const symlinkResult = checkSymlinkPathSafety(resolvedPath, [getOriginalCwd()])
|
||||
if (!symlinkResult.safe) {
|
||||
return {
|
||||
behavior: 'deny',
|
||||
message: `Output redirection to '${resolvedPath}' is blocked: contains a symlink pointing outside the working directory.`,
|
||||
decisionReason: {
|
||||
type: 'safetyCheck',
|
||||
reason: symlinkResult.reason ?? 'Symlink points outside the session working directory',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -195,6 +195,7 @@ export type GlobalConfig = {
|
||||
doctorShownAtSession?: number
|
||||
userID?: string
|
||||
theme: ThemeSetting
|
||||
locale?: string // cc-haha i18n locale (en | zh-CN | zh-TW), synced from desktop UI
|
||||
hasCompletedOnboarding?: boolean
|
||||
// Tracks the last version that reset onboarding, used with MIN_VERSION_REQUIRING_ONBOARDING_RESET
|
||||
lastOnboardingVersion?: string
|
||||
|
||||
@ -54,6 +54,8 @@ export function setLocale(l: Locale | string): void {
|
||||
if (lower === 'zh-tw' || lower === 'zh_hant') currentLocale = 'zh-TW'
|
||||
else if (lower.startsWith('zh')) currentLocale = 'zh-CN'
|
||||
else currentLocale = 'en'
|
||||
// Sync to env so child processes and the CLI startup path pick it up
|
||||
process.env.CC_HAHA_LOCALE = currentLocale
|
||||
}
|
||||
|
||||
export function getLocale(): Locale {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user