mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-09 09:14:07 +08:00
Re-review of the previous CLI-config fix batch (4fc8948) surfaced three
regressions introduced by that batch itself, plus two sinks the secret
redaction still missed:
- dotenv.ts: quoting a value that needed escaping (contains #, quotes, or
leading/trailing whitespace) injected \\ and \" escapes that the real
`dotenv` package (used by gemini-cli to read the file back) never
unescapes — it only re-expands \n/\r inside double-quoted values. This
silently corrupted Windows-style paths (doubled backslashes) and any
value containing a literal double quote. Fixed by preferring a
single-quoted value (read back 100% literally) whenever it has no
embedded single quote, and only escaping \n/\r in the double-quote
fallback.
- redact.ts: the value-matching regex stopped at the first embedded
apostrophe in a double-quoted value, or at an already-broken/empty
quoted pair, leaving the real secret exposed as an unredacted trailing
fragment in both cases. Fixed by matching the rest of the source line
(smol-toml embeds raw source lines verbatim in its own error messages)
instead of a single quoted token.
- Centralized secret redaction inside parseTomlOrThrow/parseJsonOrThrow
themselves (instead of only at the two call sites that remembered to
wrap it), closing two previously-unredacted sinks:
validateCliConfigDraftForWrite (manual config-text editing) and
draftUpdater.ts's direct parse calls.
- Added missing assertCliConfigCredentials coverage for the
missing-base-URL branches of OpenCode/Qwen/Kimi (only the missing-API-key
branch had a test before).
Every fix has a regression test verified to fail without it; the dotenv
and redact tests exercise the real `dotenv`/`smol-toml` packages directly
rather than hand-crafted strings, to catch this class of drift going
forward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
43 lines
1.9 KiB
TypeScript
43 lines
1.9 KiB
TypeScript
/** Parse a dotenv file into an ordered key-value map, preserving entry order. */
|
|
export function parseDotenv(content: string): Map<string, string> {
|
|
const out = new Map<string, string>()
|
|
for (const raw of content.split(/\r?\n/)) {
|
|
const line = raw.trim()
|
|
if (!line || line.startsWith('#')) continue
|
|
const eq = line.indexOf('=')
|
|
if (eq === -1) continue
|
|
const key = line.slice(0, eq).trim()
|
|
let value = line.slice(eq + 1).trim()
|
|
if (value.startsWith('"') && value.endsWith('"')) {
|
|
value = value.slice(1, -1).replace(/\\(["\\])/g, '$1')
|
|
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
value = value.slice(1, -1)
|
|
}
|
|
out.set(key, value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Bare (unquoted) dotenv values are truncated by standard dotenv parsers at the
|
|
// first `#`, and lose leading/trailing whitespace — quote whenever that would
|
|
// otherwise corrupt the value on read-back by the CLI tool's own loader.
|
|
function needsDotenvQuoting(value: string): boolean {
|
|
return value === '' || /^\s|\s$|["'#\\]/.test(value)
|
|
}
|
|
|
|
// The real `dotenv` package (used by the CLI tools that read these files back) only re-expands
|
|
// literal `\n`/`\r` sequences inside a double-quoted value on read — it does NOT unescape `\\` or
|
|
// `\"`, so injecting those escapes here would corrupt the value on read-back instead of preserving
|
|
// it. A single-quoted value, by contrast, is taken back 100% literally with no escape processing at
|
|
// all, so prefer it whenever the value has no embedded single quote to conflict with the wrapper.
|
|
function quoteDotenvValue(value: string): string {
|
|
if (!value.includes("'")) return `'${value}'`
|
|
return `"${value.replace(/\r/g, '\\r').replace(/\n/g, '\\n')}"`
|
|
}
|
|
|
|
export function renderDotenvFile(envMap: Map<string, string>): string {
|
|
return `${[...envMap.entries()]
|
|
.map(([key, value]) => `${key}=${needsDotenvQuoting(value) ? quoteDotenvValue(value) : value}`)
|
|
.join('\n')}\n`
|
|
}
|