Skip to content

fix(tui): decode JSON tool envelopes and fold untrusted wrappers for display - #15

Merged
jkyberneees merged 1 commit into
mainfrom
fix/tool-result-envelope-display
Jul 27, 2026
Merged

fix(tui): decode JSON tool envelopes and fold untrusted wrappers for display#15
jkyberneees merged 1 commit into
mainfrom
fix/tool-result-envelope-display

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Symptom

Tool results from tools returning a JSON envelope (read_file, search_files, head_tail) rendered as raw JSON in the transcript, exposing encoding/json HTML-escaped prompt-injection wrapper tags:

{"content":"<untrusted_content_dd96e237… source=\"/path/to/file.go\">\n217|\n218|\tta := textarea.New(…

Root cause

odek wraps untrusted tool output in <untrusted_content_<nonce> source="…">…</…>. Tools returning JSON serialize that string with Go's encoding/json, which HTML-escapes <, >, & by default. The TUI rendered the raw envelope instead of decoding it first. Display noise only — the wrapper is a prompt-injection boundary and is not altered in any data sent back to the model.

Fix (display layer only, internal/tui)

resultPreview (used by both live tool_result events and resumed transcripts) now normalizes before render:

  1. JSON envelopes: a body that parses cleanly as an object with a string content field is decoded — the real content renders, with remaining scalar metadata (e.g. total_lines) as a one-line footer. Unknown shapes (nested values, missing content, invalid JSON) render byte-identical — fail-safe, never lossy.
  2. Wrapper folding: untrusted_content wrappers become a dimmed badge line ⚠ untrusted: <source> followed by the body. Both the literal tag form (live stream events) and the `` escaped form (undecoded envelopes) are recognized.

Scope: rendering only. Nothing persisted, echoed back, or forwarded is rewritten.

Tests (TDD — written red first)

  • read_file-style envelope with escaped wrapper → no `` in output, badge shows the source path, body line numbers intact, total_lines footer present
  • literal wrapper in a live (non-JSON) result → folded badge
  • plain non-JSON output / non-envelope JSON / nested-metadata envelope → byte-identical
  • malformed (truncated) envelope → rendered raw, unchanged

Verification

  • make fmt && make vet && make lint — 0 issues
  • make test — full -race suite green
  • go build ./...

…display

Tool results that return a JSON envelope (read_file, search_files,
head_tail) rendered as raw JSON, exposing encoding/json's HTML-escaped
(<) prompt-injection wrapper tags as transcript noise.

resultPreview now normalizes for display only:

- envelopes with a string content field are decoded, rendering the real
  content with remaining scalar metadata as a one-line footer
- untrusted_content wrappers (literal and < escaped forms) fold
  into a "⚠ untrusted: <source>" badge line followed by the body
- unknown or malformed shapes render byte-identical (fail-safe)

Rendering only: persisted and model-bound data is never rewritten.
@jkyberneees
jkyberneees merged commit 72fff76 into main Jul 27, 2026
5 checks passed
@jkyberneees
jkyberneees deleted the fix/tool-result-envelope-display branch July 27, 2026 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant