Agent chat: images a reply shows from outside the session's folders display inline (read-only)
Summary
In Leo's nightly, a reply's  drew as a locked file card (image icon, "after", the file name, a lock, Open) instead of the screenshot, because /tmp is outside the session's folders. The image was fine; hiding it behind Open was not.
Now an image file a reply shows loads inline even from outside the session's folders, read-only and for display only:
- Host (
loadReplyImage, newAgentPaneReplyImages.outside): an outside path loads only when it has a raster image extension (png, jpg, jpeg, gif, webp, heic, tif, tiff, bmp) and decodes as one through ImageIO, at most 10 MB. SVG and PDF outside the folders stay cards. No other bytes ever reach the page. - The deny list (
~/.ssh,~/.gnupg,~/Library/Keychains,~/.aws,~/.config/gh,*.pem,*.key,.env*) is still refused, checked again on the canonical path, so a harmless name that links to a secret is refused too. agentPane.links.outsideRoots = text(the strictest setting) keeps refusing: the page doesn't ask, and the image stays the card without Open. Under the default (open) andconfirm, outside images load.- Every outside refusal answers
link.path_outside_roots, so the page learns only whether an image showed, not whether some other file exists. - Page (
ReplyImage.tsx, one line): an outside path is loadable unlesspolicy.outsideRoots === "text". The outside card (lock and Open) is still what draws when the host refuses or the load fails. This edit is kept to that line and its comments so it merges cleanly with #23732, which changes the same file.
The AgentPaneReplyRequest header doc comment and plans/cmux-next/agent-pane-reply-links.md (D5) describe the new rule. AgentPaneReplySetting.swift is left unchanged, so the settings export stamp stays fresh.
The outside file is opened once with O_NOFOLLOW | O_NONBLOCK; its type (regular file) and size are checked with fstat on that handle and it is read with a bounded read, so it cannot be swapped between the checks and the read.
Testing
- New
webviews/test/reply-image-outside.test.tsx(JSDOM): an outside image loads underopenandconfirm, is never requested undertext(the card shows, no Open), and draws the card with Open when the host refuses. Ran on cmux16: it fails on the baseReplyImage.tsx(Expected to contain "/tmp/ams-shots/after-messages.png", Received: []) and passes on this head, together with reply-image-reload, reply-image-cache and gallery-agent-pane-entries (6 pass, 1 skip). bun run checkinwebviews/on cmux16: passes. The one warning, from check-system-blue, is aboutimportCards.css, a file this PR doesn't touch.- New
AgentPaneReplyImageOutsideTests(Swift Testing): an outside PNG returns its data URL underopenandconfirm; undertextit's refused; deny-listed paths (~/.ssh/shot.png,server.key, and a symlink pointing into~/.ssh) are refused withlink.path_denied; a text file, a fake.png, a real PNG named.dat, an SVG, a missing file and a folder outside the roots are all refused withlink.path_outside_roots; an image inside the roots still loads undertext. - Fleet compile (
cmux-ci compile-check --ref 6633430a7f885ee955cb726ef1d2d8c78fb6820d --app): passed (job 143d3288bde54a0df6f40464). python3 scripts/cmux-next/prepush-gate.py: no new violations.
Observed / Not run
- Observed: the webview test above going red on base and green on head,
bun run check, the app compile, and the Swift suites: - Swift suites on the fleet (
cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=AgentPaneReplyImageOutsideTests,AgentPaneImageActionsTests, step 645d39600f0c33c2d1adc9dd at 6633430a): AgentPaneReplyImageOutsideTests 5/5 and AgentPaneImageActionsTests 3/3 passed. - Not run: a live check in a tagged build. Before #23732 lands, an outside image may show the locked card for a moment while it loads.
Changelog
Changed: Images an agent shows in a reply from outside the chat's folders (like a screenshot in /tmp) now display inline instead of as a locked file card, unless Files Outside the Project is set to text. Keys, .env files and credential folders are never shown.
Proof
No capture: image loading policy; webview states in the gallery. The agent-pane.reply-image gallery entry's outside-roots variant now shows the image, outside-roots-refused shows the card with lock and Open, and outside-roots-text shows the card without Open.
Checklist
- Behavior changes have added or updated tests
- No new user-facing strings (localization unaffected)
š¤ Generated with Claude Code
Agent session: cosmic-gray