Skip to content

Commit 20e7854

Browse files
greynewellclaude
andcommitted
fix: rune-safe byte slicing in token estimator, error snippets, and maskKey
- restore/render.go: countTokens used len(text)/4 (bytes) instead of utf8.RuneCountInString(text)/4 (runes) — inflated estimate for any text containing multi-byte characters (CJK, emoji, accented chars) - api/client.go: HTTP error snippet was truncated at byte offset 300, risking invalid UTF-8 when a multi-byte character straddles that boundary - setup/wizard.go: maskKey sliced the key at byte positions 8 and len-4; same class of bug fixed elsewhere in the codebase Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent af77745 commit 20e7854

3 files changed

Lines changed: 8 additions & 6 deletions

File tree

internal/api/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,8 @@ func (c *Client) request(ctx context.Context, method, path, contentType string,
313313
return &apiErr
314314
}
315315
snippet := string(respBody)
316-
if len(snippet) > 300 {
317-
snippet = snippet[:300] + "..."
316+
if runes := []rune(snippet); len(runes) > 300 {
317+
snippet = string(runes[:300]) + "..."
318318
}
319319
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, snippet)
320320
}

internal/restore/render.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
gotmpl "text/template"
88
"time"
99
"unicode"
10+
"unicode/utf8"
1011
)
1112

1213
const maxCyclesToShow = 10
@@ -288,7 +289,7 @@ func CountTokens(text string) int {
288289
inWord = true
289290
}
290291
}
291-
charEstimate := len(text) / 4
292+
charEstimate := utf8.RuneCountInString(text) / 4
292293
wordEstimate := words * 100 / 75
293294
if charEstimate > wordEstimate {
294295
return charEstimate

internal/setup/wizard.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,11 @@ func boolPtr(b bool) *bool { return &b }
194194

195195
// maskKey returns a display-safe version of the API key.
196196
func maskKey(key string) string {
197-
if len(key) <= 12 {
198-
return strings.Repeat("*", len(key))
197+
runes := []rune(key)
198+
if len(runes) <= 12 {
199+
return strings.Repeat("*", len(runes))
199200
}
200-
return key[:8] + "..." + key[len(key)-4:]
201+
return string(runes[:8]) + "..." + string(runes[len(runes)-4:])
201202
}
202203

203204
// findGitRoot detects the git root from the current working directory.

0 commit comments

Comments
 (0)