detached 5023d63

fix: deduplicate JS escape helper + add clipboard payload size limit

- Remove duplicate escapeForJSInsert, reuse escapeForJSString from
  content_injector.go (same package)
- Add 10MB size limit on /api/clipboard-set to prevent memory exhaustion
commit
5023d63f5938d664fedd6de3522b0c4f99e85d4b
author
brice · · 2026-03-25 06:45 UTC
committer
brice · · 2026-03-28 04:39 UTC
parent
48e3ed9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +8 -12
M internal/infrastructure/cef/input_bridge.go +2 -12
@@ -2,7 +2,6 @@ package cef
22
33 import (
44 "context"
5- "strings"
65 "sync"
76
87 purecef "github.com/bnema/purego-cef/cef"
@@ -463,7 +462,7 @@ func (ib *inputBridge) pasteFromClipboard() {
463462 }
464463
465464 // Escape the text for embedding in a JS string literal.
466- escaped := escapeForJSInsert(text)
465+ escaped := escapeForJSString(text)
467466 js := "document.execCommand('insertText',false,'" + escaped + "')"
468467 frame.ExecuteJavaScript(js, "", 0)
469468 log.Debug().Msg("cef: paste JS executed")
@@ -473,16 +472,7 @@ func (ib *inputBridge) pasteFromClipboard() {
473472 log.Debug().Msg("cef: ReadTextAsync dispatched")
474473 }
475474
476-// escapeForJSInsert escapes text for safe embedding in a JS single-quoted string.
477-func escapeForJSInsert(s string) string {
478- s = strings.ReplaceAll(s, "\\", "\\\\")
479- s = strings.ReplaceAll(s, "'", "\\'")
480- s = strings.ReplaceAll(s, "\n", "\\n")
481- s = strings.ReplaceAll(s, "\r", "\\r")
482- s = strings.ReplaceAll(s, "\u2028", "\\u2028")
483- s = strings.ReplaceAll(s, "\u2029", "\\u2029")
484- return s
485-}
475+// escapeForJSString is defined in content_injector.go — reused here.
486476
487477 // ---------------------------------------------------------------------------
488478 // Translation helpers
@@ -2,7 +2,6 @@ package cef
2 2
3 import (3 import (
4 "context"4 "context"
5- "strings"
6 "sync"5 "sync"
7 6
8 purecef "github.com/bnema/purego-cef/cef"7 purecef "github.com/bnema/purego-cef/cef"
@@ -463,7 +462,7 @@ func (ib *inputBridge) pasteFromClipboard() {
463 }462 }
464 463
465 // Escape the text for embedding in a JS string literal.464 // Escape the text for embedding in a JS string literal.
466- escaped := escapeForJSInsert(text)465+ escaped := escapeForJSString(text)
467 js := "document.execCommand('insertText',false,'" + escaped + "')"466 js := "document.execCommand('insertText',false,'" + escaped + "')"
468 frame.ExecuteJavaScript(js, "", 0)467 frame.ExecuteJavaScript(js, "", 0)
469 log.Debug().Msg("cef: paste JS executed")468 log.Debug().Msg("cef: paste JS executed")
@@ -473,16 +472,7 @@ func (ib *inputBridge) pasteFromClipboard() {
473 log.Debug().Msg("cef: ReadTextAsync dispatched")472 log.Debug().Msg("cef: ReadTextAsync dispatched")
474 }473 }
475 474
476-// escapeForJSInsert escapes text for safe embedding in a JS single-quoted string.475+// escapeForJSString is defined in content_injector.go — reused here.
477-func escapeForJSInsert(s string) string {
478- s = strings.ReplaceAll(s, "\\", "\\\\")
479- s = strings.ReplaceAll(s, "'", "\\'")
480- s = strings.ReplaceAll(s, "\n", "\\n")
481- s = strings.ReplaceAll(s, "\r", "\\r")
482- s = strings.ReplaceAll(s, "\u2028", "\\u2028")
483- s = strings.ReplaceAll(s, "\u2029", "\\u2029")
484- return s
485-}
486 476
487 // ---------------------------------------------------------------------------477 // ---------------------------------------------------------------------------
488 // Translation helpers478 // Translation helpers
M internal/infrastructure/cef/scheme_handler.go +6 -0
@@ -216,6 +216,8 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource
216216
217217 // handleClipboardSet receives copied text from JS copy/cut events and writes
218218 // it to the system clipboard via the engine callback.
219+const maxClipboardBytes = 10 << 20 // 10 MB
220+
219221 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {
220222 h.logger.Debug().Msg("cef: /api/clipboard-set request received")
221223
@@ -224,6 +226,10 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.
224226 h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)")
225227 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})
226228 }
229+ if len(body) > maxClipboardBytes {
230+ h.logger.Warn().Int("body_len", len(body)).Msg("cef: clipboard-set — payload too large")
231+ return h.newJSONResourceHandler(http.StatusRequestEntityTooLarge, map[string]string{"error": "payload too large"})
232+ }
227233
228234 var payload struct {
229235 Text string `json:"text"`
@@ -216,6 +216,8 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource
216 216
217 // handleClipboardSet receives copied text from JS copy/cut events and writes217 // handleClipboardSet receives copied text from JS copy/cut events and writes
218 // it to the system clipboard via the engine callback.218 // it to the system clipboard via the engine callback.
219+const maxClipboardBytes = 10 << 20 // 10 MB
220+
219 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {221 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {
220 h.logger.Debug().Msg("cef: /api/clipboard-set request received")222 h.logger.Debug().Msg("cef: /api/clipboard-set request received")
221 223
@@ -224,6 +226,10 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.
224 h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)")226 h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)")
225 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})227 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})
226 }228 }
229+ if len(body) > maxClipboardBytes {
230+ h.logger.Warn().Int("body_len", len(body)).Msg("cef: clipboard-set — payload too large")
231+ return h.newJSONResourceHandler(http.StatusRequestEntityTooLarge, map[string]string{"error": "payload too large"})
232+ }
227 233
228 var payload struct {234 var payload struct {
229 Text string `json:"text"`235 Text string `json:"text"`

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close