Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 105 additions & 11 deletions deploy/cloudflare-worker/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1635,9 +1635,113 @@ function hexFromBytes(bytes) {
return out;
}

function sanitizeTranscriptEntries(entries) {
const sanitized = [];
for (const entry of Array.isArray(entries) ? entries : []) {
if (!entry || typeof entry !== "object") continue;
const next = { ...entry };
if (next.kind === "message") {
const text = redactInternalContext(next.text).trim();
next.text = text === "[internal context omitted]" ? "" : text;
const images = Array.isArray(next.images) ? next.images : [];
if (!next.text && !images.length && !Number(next.omitted_images || 0)) continue;
}
if (next.kind === "tool") {
next.input_preview = redactInternalContext(next.input_preview);
next.output = redactInternalContext(next.output);
next.output_bytes = byteLengthInBrowser(next.output || "");
}
sanitized.push(next);
}
return sanitized;
}

function redactInternalContext(text) {
if (!text) return "";
const visible = [];
let inHiddenBlock = false;
let silentHiddenBlock = false;
let redactedPrevious = false;
for (const line of String(text).split("\\n")) {
const trimmed = line.trim();
if (inHiddenBlock) {
if (!silentHiddenBlock && !redactedPrevious) {
visible.push("[internal context omitted]");
redactedPrevious = true;
}
if (endsInternalContext(trimmed)) {
inHiddenBlock = false;
silentHiddenBlock = false;
}
continue;
}
if (containsInternalContext(trimmed)) {
const silent = silentlyRedactsInternalContext(trimmed);
if (!silent && !redactedPrevious) {
visible.push("[internal context omitted]");
redactedPrevious = true;
}
if (startsInternalContext(trimmed) && !endsInternalContext(trimmed)) {
inHiddenBlock = true;
silentHiddenBlock = silent;
}
continue;
}
visible.push(line);
redactedPrevious = false;
}
return visible.join("\\n").trim();
}

function silentlyRedactsInternalContext(text) {
return String(text || "").trim().startsWith("<oai-mem-citation>");
}

function containsInternalContext(text) {
const value = String(text || "");
if (startsInternalContext(value)) return true;
return [
"# AGENTS.md instructions",
"<recommended_plugins>",
"<codex_internal_context",
"<environment_context>",
"<INSTRUCTIONS>",
"<skill>",
"<turn_aborted>",
"<oai-mem-citation>"
].some((marker) => value.includes(marker));
}

function startsInternalContext(text) {
const value = String(text || "").trim();
return [
"# AGENTS.md instructions",
"<recommended_plugins>",
"<codex_internal_context",
"<environment_context>",
"<INSTRUCTIONS>",
"<skill>",
"<turn_aborted>",
"<oai-mem-citation>"
].some((prefix) => value.startsWith(prefix));
}

function endsInternalContext(text) {
const value = String(text || "").trim();
return [
"</recommended_plugins>",
"</codex_internal_context>",
"</environment_context>",
"</INSTRUCTIONS>",
"</skill>",
"</turn_aborted>",
"</oai-mem-citation>"
].some((suffix) => value.includes(suffix));
}

function renderTranscript(transcript, options = {}) {
activeTranscriptSource = String(transcript.source || transcript.source_agent || "");
const entries = (transcript.entries || []).filter((entry) => !isInternalContextEntry(entry));
const entries = sanitizeTranscriptEntries(transcript.entries || []);
const messageCount = entries.filter((entry) => entry.kind === "message").length;
const toolCount = entries.filter((entry) => entry.kind === "tool").length;
const imageCount = entries.reduce((count, entry) => count + (entry.images || []).filter((image) => !image.omitted).length, 0);
Expand Down Expand Up @@ -1695,16 +1799,6 @@ function lastAssistantMessageIndex(entries) {
return -1;
}

function isInternalContextEntry(entry) {
if (!entry || entry.kind !== "message") return false;
const text = String(entry.text || "").trim();
return text.startsWith("# AGENTS.md instructions for ") ||
text.startsWith("<codex_internal_context") ||
text.startsWith("<environment_context>") ||
text.startsWith("<INSTRUCTIONS>") ||
text.startsWith("<skill>");
}

function messageNode(entry) {
const article = document.createElement("article");
article.className = "message-row " + (entry.role || "");
Expand Down
93 changes: 92 additions & 1 deletion deploy/cloudflare-worker/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ test("share page serves human preview shell and agent metadata", async () => {
assert.match(html, /function toolActionNode/);
assert.match(html, /function renderMarkdown/);
assert.match(html, /function imageGallery/);
assert.match(html, /function isInternalContextEntry/);
assert.match(html, /function sanitizeTranscriptEntries/);
assert.match(html, /function redactInternalContext/);
assert.match(html, /const entries = sanitizeTranscriptEntries\(transcript\.entries \|\| \[\]\)/);
assert.match(html, /skill-chip/);
assert.match(html, /function skillDetailsNode/);
assert.match(html, /function stripSkillInvocation/);
Expand Down Expand Up @@ -289,6 +291,95 @@ test("share page serves human preview shell and agent metadata", async () => {
assert.equal(manifestJSON.import.skill_url, "https://github.com/z2z23n0/agent-capsule/tree/main/skills/agent-capsule");
});

test("share page redacts internal context from full transcript entries", async () => {
const env = fakeEnv();
const upload = await worker.fetch(new Request(BASE_URL + "/v1/shares", {
method: "POST",
body: shareForm(new Blob(["hello"]))
}), env);
assert.equal(upload.status, 201);
const created = await upload.json();
const html = await (await worker.fetch(new Request(created.share_url), env)).text();
const entries = [
{
kind: "message",
role: "user",
text: [
"<recommended_plugins>",
"private plugin inventory",
"</recommended_plugins>",
"# AGENTS.md instructions",
"<INSTRUCTIONS>",
"private project rules",
"</INSTRUCTIONS>",
"<environment_context>",
"/Users/private/workspace",
"</environment_context>"
].join("\n")
},
{
kind: "message",
role: "assistant",
text: [
"visible final answer",
"",
"<oai-mem-citation>",
"<citation_entries>",
"MEMORY.md:10-12|note=[private provenance]",
"</citation_entries>",
"<rollout_ids>",
"019f523a-60d4-70d2-893e-c93ac9f92f43",
"</rollout_ids>",
"</oai-mem-citation>"
].join("\n")
},
{
kind: "tool",
tool: "exec",
input_preview: "visible input before\n<environment_context>\nprivate input\n</environment_context>\nvisible input after",
output: "visible output before\n# AGENTS.md instructions\n<INSTRUCTIONS>\nprivate output\n</INSTRUCTIONS>\nvisible output after"
}
];

const sanitized = runSharePageFunction(
html,
[
"sanitizeTranscriptEntries",
"redactInternalContext",
"containsInternalContext",
"startsInternalContext",
"endsInternalContext",
"silentlyRedactsInternalContext",
"byteLengthInBrowser"
],
"sanitizeTranscriptEntries(entries)",
{ entries }
);
const visible = JSON.stringify(sanitized);
for (const leaked of [
"recommended_plugins",
"private plugin inventory",
"AGENTS.md",
"private project rules",
"environment_context",
"/Users/private/workspace",
"oai-mem-citation",
"citation_entries",
"MEMORY.md",
"private provenance",
"rollout_ids",
"019f523a",
"private input",
"private output"
]) {
assert.doesNotMatch(visible, new RegExp(leaked.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
}
assert.equal(sanitized.length, 2);
assert.equal(sanitized[0].text, "visible final answer");
assert.match(sanitized[1].input_preview, /visible input before[\s\S]*\[internal context omitted\][\s\S]*visible input after/);
assert.match(sanitized[1].output, /visible output before[\s\S]*\[internal context omitted\][\s\S]*visible output after/);
});

test("share page renders a complete Chinese shell only when explicitly requested", async () => {
const env = fakeEnv();
const upload = await worker.fetch(new Request(BASE_URL + "/v1/shares", {
Expand Down
27 changes: 27 additions & 0 deletions internal/capsule/capsule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,33 @@ func TestPreviewTranscriptRedactsInternalContextAcrossMessageParts(t *testing.T)
}
}

func TestPreviewTranscriptRedactsMemoryCitationSuffix(t *testing.T) {
manifest := Manifest{
ThreadID: testThreadID,
ThreadTitle: "Preview demo",
CreatedAt: "2026-07-23T00:00:00Z",
}
session := strings.Join([]string{
`{"timestamp":"2026-07-23T00:00:01Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"visible final answer\n\n<oai-mem-citation>\n<citation_entries>\nMEMORY.md:10-12|note=[private provenance]\n</citation_entries>\n<rollout_ids>\n019f523a-60d4-70d2-893e-c93ac9f92f43\n</rollout_ids>\n</oai-mem-citation>"}]}}`,
}, "\n") + "\n"
transcript := buildPreviewTranscript(manifest, []byte(session))
if transcript.MessageCount != 1 {
t.Fatalf("message_count = %d", transcript.MessageCount)
}
if len(transcript.Entries) != 1 || transcript.Entries[0].Text != "visible final answer" {
t.Fatalf("visible answer = %#v", transcript.Entries)
}
preview := previewEntriesText(transcript)
if !strings.Contains(preview, "visible final answer") {
t.Fatalf("preview lost visible answer:\n%s", preview)
}
for _, leaked := range []string{"oai-mem-citation", "citation_entries", "MEMORY.md", "private provenance", "rollout_ids", "019f523a"} {
if strings.Contains(preview, leaked) {
t.Fatalf("memory citation leaked %q into preview:\n%s", leaked, preview)
}
}
}

func TestPreviewTranscriptAttachesSkillMessages(t *testing.T) {
manifest := Manifest{
ThreadID: testThreadID,
Expand Down
18 changes: 16 additions & 2 deletions internal/capsule/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,27 +470,31 @@ func previewRedactHiddenContext(text string) string {
}
var b strings.Builder
inHiddenBlock := false
silentHiddenBlock := false
redactedPrevious := false
for _, part := range strings.SplitAfter(text, "\n") {
line := strings.TrimRight(part, "\r\n")
trimmed := strings.TrimSpace(line)
if inHiddenBlock {
if !redactedPrevious {
if !silentHiddenBlock && !redactedPrevious {
b.WriteString("[internal context omitted]\n")
redactedPrevious = true
}
if previewEndsHiddenContext(trimmed) {
inHiddenBlock = false
silentHiddenBlock = false
}
continue
}
if previewContainsHiddenContext(trimmed) {
if !redactedPrevious {
silent := previewSilentlyRedactsContext(trimmed)
if !silent && !redactedPrevious {
b.WriteString("[internal context omitted]\n")
redactedPrevious = true
}
if previewStartsHiddenContext(trimmed) && !previewEndsHiddenContext(trimmed) {
inHiddenBlock = true
silentHiddenBlock = silent
}
continue
}
Expand All @@ -500,6 +504,10 @@ func previewRedactHiddenContext(text string) string {
return b.String()
}

func previewSilentlyRedactsContext(text string) bool {
return strings.HasPrefix(strings.TrimSpace(text), "<oai-mem-citation>")
}

func previewContainsHiddenContext(text string) bool {
if previewStartsHiddenContext(text) {
return true
Expand All @@ -511,6 +519,8 @@ func previewContainsHiddenContext(text string) bool {
"<environment_context>",
"<INSTRUCTIONS>",
"<skill>",
"<turn_aborted>",
"<oai-mem-citation>",
} {
if strings.Contains(text, marker) {
return true
Expand All @@ -527,6 +537,8 @@ func previewStartsHiddenContext(text string) bool {
"<environment_context>",
"<INSTRUCTIONS>",
"<skill>",
"<turn_aborted>",
"<oai-mem-citation>",
} {
if strings.HasPrefix(text, prefix) {
return true
Expand All @@ -542,6 +554,8 @@ func previewEndsHiddenContext(text string) bool {
"</environment_context>",
"</INSTRUCTIONS>",
"</skill>",
"</turn_aborted>",
"</oai-mem-citation>",
} {
if strings.HasPrefix(text, prefix) {
return true
Expand Down
Loading