@@ -60,6 +60,21 @@ const maxBrowserElements = 500
6060// history limit cannot be bypassed by a small number of huge pages.
6161const maxBrowserSnapshotBytes = 1 * 1024 * 1024
6262
63+ // Per-field caps for page-derived strings kept outside Content (title, element
64+ // text, link URLs). With maxBrowserElements they bound the whole snapshot.
65+ const (
66+ maxBrowserFieldBytes = 2 * 1024
67+ maxBrowserURLBytes = 8 * 1024
68+ )
69+
70+ // capBrowserField clamps a single page-derived string to maxBrowserFieldBytes.
71+ func capBrowserField (s string ) string {
72+ if len (s ) <= maxBrowserFieldBytes {
73+ return s
74+ }
75+ return truncateUTF8Safe (s , maxBrowserFieldBytes )
76+ }
77+
6378// truncatePageContent caps page text at maxBrowserSnapshotBytes, backing up
6479// to a UTF-8 rune boundary so a multibyte character split by the cap never
6580// ships U+FFFD mojibake, and appends a truncation marker.
@@ -117,7 +132,7 @@ func (t *browserTool) checkRedirect(req *http.Request, via []*http.Request) erro
117132 return fmt .Errorf ("stopped after 10 redirects" )
118133 }
119134 target := req .URL .String ()
120- risk := danger . ClassifyURL (target )
135+ risk := ssrfClassifyURL (target )
121136 if err := t .dangerousConfig .CheckOperation (danger.ToolOperation {
122137 Name : "browser" , Resource : target , Risk : risk ,
123138 }, t .trustedClasses ); err != nil {
@@ -229,7 +244,7 @@ func (t *browserTool) doNavigate(rawURL string) (string, error) {
229244 req .Header .Set ("Accept" , "text/html,application/xhtml+xml" )
230245
231246 // Security: classify and check browser operation
232- risk := danger . ClassifyURL (rawURL )
247+ risk := ssrfClassifyURL (rawURL )
233248 if err := t .dangerousConfig .CheckOperation (danger.ToolOperation {
234249 Name : "browser" , Resource : rawURL , Risk : risk ,
235250 }, t .trustedClasses ); err != nil {
@@ -378,7 +393,7 @@ func parseHTML(ctx context.Context, html, pageURL string, status int) browserSna
378393
379394 // Extract title
380395 if m := reTitle .FindStringSubmatch (html ); len (m ) > 1 {
381- snap .Title = strings .TrimSpace (m [1 ])
396+ snap .Title = capBrowserField ( strings .TrimSpace (m [1 ]) )
382397 }
383398
384399 var contentParts []string
@@ -416,12 +431,12 @@ func parseHTML(ctx context.Context, html, pageURL string, status int) browserSna
416431 break
417432 }
418433 href := strings .TrimSpace (m [1 ])
419- text := strings .TrimSpace (m [2 ])
434+ text := capBrowserField ( strings .TrimSpace (m [2 ]) )
420435 // Skip non-navigable schemes. The check is case-insensitive and
421436 // covers javascript:, data:, and vbscript: — all of which can carry
422437 // script/active content the agent must never be sent into.
423438 lowerHref := strings .ToLower (href )
424- if href == "" || text == "" || href == "#" ||
439+ if href == "" || text == "" || href == "#" || len ( href ) > maxBrowserURLBytes ||
425440 strings .HasPrefix (lowerHref , "javascript:" ) ||
426441 strings .HasPrefix (lowerHref , "data:" ) ||
427442 strings .HasPrefix (lowerHref , "vbscript:" ) {
@@ -451,7 +466,7 @@ func parseHTML(ctx context.Context, html, pageURL string, status int) browserSna
451466 if len (elements ) >= maxBrowserElements {
452467 break
453468 }
454- text := strings .TrimSpace (m [1 ])
469+ text := capBrowserField ( strings .TrimSpace (m [1 ]) )
455470 if text == "" {
456471 text = "button"
457472 }
@@ -476,6 +491,7 @@ func parseHTML(ctx context.Context, html, pageURL string, status int) browserSna
476491 } else if pm := reInputPlaceholder .FindStringSubmatch (tag ); len (pm ) > 1 {
477492 text = "[" + pm [1 ] + "]"
478493 }
494+ text = capBrowserField (text )
479495 if text == "" {
480496 text = "input"
481497 }
0 commit comments