Protect inline visualization viewers from sandbox writes (#38306)

## Why

Inline visualization viewer documents must remain outside locations that a
sandboxed session can modify before they are opened in a browser.

## What changed

- Materialize viewer documents in a dedicated cache under `CODEX_HOME`, keyed
  by the source and artifact thread IDs, instead of alongside visualization
  artifacts.
- Create visualization links only when the active filesystem policy cannot
  write to the viewer cache. This also disables links for full-disk-write
  sessions and applies the policy consistently when loading transcript history.
- Reject viewer cache paths containing symbolic links and track materialized
  documents in memory so unchanged viewers are reused without trusting existing
  file contents.

## Testing

- Verify that rendering an unchanged visualization reuses the existing viewer
  file, while an updated fragment refreshes it.

GitOrigin-RevId: 12b7fbe522a68076e5d683a987b7801940d131c2
This commit is contained in:
viyatb-oai
2026-08-13 04:08:23 +00:00
committed by copyberry
parent 363427b5e3
commit 902bd9e06b
10 changed files with 116 additions and 52 deletions
+4 -4
View File
@@ -24,6 +24,7 @@ use crate::history_cell::ReasoningSummaryCell;
use crate::history_cell::SessionInfoCell;
use crate::history_cell::UserHistoryCell;
use crate::history_cell::raw_lines_from_source;
use crate::legacy_core::config::Config;
use crate::thread_transcript::RawReasoningVisibility;
use crate::thread_transcript::thread_items_to_transcript_cells;
@@ -42,12 +43,11 @@ impl App {
} else {
RawReasoningVisibility::Hidden
};
let codex_home = self.config.codex_home.to_path_buf();
let cells = load_export_transcript(
app_server,
thread_id,
visibility,
Some(codex_home.as_path()),
Some(&self.config),
self.transcript_cells.clone(),
)
.await?;
@@ -77,7 +77,7 @@ pub(super) async fn load_export_transcript(
app_server: &mut AppServerSession,
thread_id: ThreadId,
visibility: RawReasoningVisibility,
codex_home: Option<&Path>,
config: Option<&Config>,
visible_transcript: Vec<Arc<dyn HistoryCell>>,
) -> Result<Vec<Arc<dyn HistoryCell>>, String> {
let mut thread = app_server
@@ -123,7 +123,7 @@ pub(super) async fn load_export_transcript(
&thread.cwd,
[item],
visibility,
codex_home,
config,
));
}
}