mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
fix: 代码审查修复 — 安全、性能和正确性
- triggersApi: 添加 assertSubscriptionBaseUrl 防止 OAuth token 泄露 - claude.ts: 修复流式响应 O(n^2) 字符串拼接,改用数组累积 - claude.ts: 移除未使用的 import,动态 import 改为静态 import - StatusLine: BuiltinStatusLine 仅在 statusLineEnabled 时显示,修复双行问题 - local-vault: 修复 --reveal 标志位置解析 bug - share: 修复 sk-proj-* OpenAI 密钥未脱敏问题 - store.ts: 临时文件改用同目录创建,避免跨文件系统 rename 失败 - store.ts: 添加空字符串 key 校验 - permissionValidation: 端口正则限制为有效 TCP 范围 0-65535 - 测试 mock 补全: schedule/vault/skill-store 测试文件 - 移除过期的 biome-ignore 注释 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
@@ -20,6 +20,7 @@ import type {
|
||||
import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
|
||||
import type { Stream } from '@anthropic-ai/sdk/streaming.mjs'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { existsSync, unlinkSync } from 'node:fs'
|
||||
import {
|
||||
getAPIProvider,
|
||||
isFirstPartyAnthropicBaseUrl,
|
||||
@@ -124,7 +125,6 @@ import {
|
||||
getAfkModeHeaderLatched,
|
||||
getCacheEditingHeaderLatched,
|
||||
getFastModeHeaderLatched,
|
||||
getLastApiCompletionTimestamp,
|
||||
getPromptCache1hAllowlist,
|
||||
getPromptCache1hEligible,
|
||||
getSessionId,
|
||||
@@ -254,7 +254,6 @@ import {
|
||||
type NonNullableUsage,
|
||||
} from './logging.js'
|
||||
import {
|
||||
CACHE_TTL_1HOUR_MS,
|
||||
checkResponseForCacheBreak,
|
||||
recordPromptState,
|
||||
} from './promptCacheBreakDetection.js'
|
||||
@@ -1431,8 +1430,6 @@ async function* queryModel(
|
||||
// unique ephemeral nonce comment to the system prompt so the prefix-cache
|
||||
// hash changes for this request, forcing a cache miss.
|
||||
{
|
||||
const { existsSync, unlinkSync } = await import('node:fs')
|
||||
const { randomUUID } = await import('node:crypto')
|
||||
const onceMarker = getBreakCacheMarkerPath()
|
||||
const alwaysFlag = getBreakCacheAlwaysPath()
|
||||
const shouldBreak = existsSync(onceMarker) || existsSync(alwaysFlag)
|
||||
@@ -1842,6 +1839,7 @@ async function* queryModel(
|
||||
let ttftMs = 0
|
||||
let partialMessage: BetaMessage | undefined
|
||||
const contentBlocks: (BetaContentBlock | ConnectorTextBlock)[] = []
|
||||
const textDeltas = new Map<number, string[]>()
|
||||
let usage: NonNullableUsage = EMPTY_USAGE
|
||||
let costUSD = 0
|
||||
let stopReason: BetaStopReason | null = null
|
||||
@@ -1940,6 +1938,7 @@ async function* queryModel(
|
||||
ttftMs = 0
|
||||
partialMessage = undefined
|
||||
contentBlocks.length = 0
|
||||
textDeltas.clear()
|
||||
usage = EMPTY_USAGE
|
||||
stopReason = null
|
||||
isAdvisorInProgress = false
|
||||
@@ -2096,6 +2095,7 @@ async function* queryModel(
|
||||
}
|
||||
break
|
||||
case 'text':
|
||||
textDeltas.set(part.index, [])
|
||||
contentBlocks[part.index] = {
|
||||
...part.content_block,
|
||||
// awkwardly, the sdk sometimes returns text as part of a
|
||||
@@ -2202,7 +2202,7 @@ async function* queryModel(
|
||||
})
|
||||
throw new Error('Content block is not a text block')
|
||||
}
|
||||
;(contentBlock as { text: string }).text += delta.text
|
||||
textDeltas.get(part.index)?.push(delta.text!)
|
||||
break
|
||||
case 'signature_delta':
|
||||
if (
|
||||
@@ -2270,6 +2270,12 @@ async function* queryModel(
|
||||
})
|
||||
throw new Error('Message not found')
|
||||
}
|
||||
// Merge accumulated text deltas into the content block (O(n) join instead of O(n^2) +=)
|
||||
const deltas = textDeltas.get(part.index)
|
||||
if (deltas) {
|
||||
;(contentBlock as { text: string }).text = deltas.join('')
|
||||
textDeltas.delete(part.index)
|
||||
}
|
||||
const m: AssistantMessage = {
|
||||
message: {
|
||||
...partialMessage,
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
rmSync,
|
||||
} from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { homedir, tmpdir } from 'node:os'
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { logError } from '../../utils/log.js'
|
||||
import { KeychainUnavailableError, tryKeychain } from './keychain.js'
|
||||
@@ -304,8 +304,9 @@ async function writeVaultFile(data: VaultFile): Promise<void> {
|
||||
}
|
||||
const filePath = getVaultFilePath()
|
||||
// C1: atomic write — tmp file + rename (POSIX rename(2) is atomic)
|
||||
const vaultDir = join(filePath, '..')
|
||||
const tmpPath = join(
|
||||
tmpdir(),
|
||||
vaultDir,
|
||||
`.local-vault-${randomBytes(8).toString('hex')}.tmp`,
|
||||
)
|
||||
try {
|
||||
@@ -340,6 +341,8 @@ async function getOrCreateSalt(vaultData: VaultFile): Promise<Buffer> {
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function setSecret(key: string, value: string): Promise<void> {
|
||||
if (!key) throw new Error('key must not be empty')
|
||||
|
||||
// D1: Guard against unbounded value sizes
|
||||
const byteLength = Buffer.byteLength(value, 'utf8')
|
||||
if (byteLength > MAX_SECRET_BYTES) {
|
||||
|
||||
Reference in New Issue
Block a user