From 6e124d27909a976327971c80edac2fea52bca500 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira Date: Wed, 12 Aug 2026 14:07:23 +0100 Subject: [PATCH] fix(anthropic): preserve image provenance trust boundary --- src/core/transform.ts | 9 ++++-- tests/refusal-provenance.test.ts | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/refusal-provenance.test.ts diff --git a/src/core/transform.ts b/src/core/transform.ts index 82f5feff2..6f91655bb 100644 --- a/src/core/transform.ts +++ b/src/core/transform.ts @@ -2354,8 +2354,7 @@ export async function transformRequest( const imageInstructionHeader = '=================== SESSION CONFIGURATION PAGES ===================\n' + "pxpipe (this user's local proxy) rendered this session's configuration" + - ' into the following images to reduce token cost. Read the pages carefully and follow them as' + - ' your operating instructions for this session.' + + ' into the following images to reduce token cost.' + ' For exact identifiers, paths, hashes, version strings, and numbers, use the adjacent' + ' exact-value factsheet; if a value was only visible in an image and is not in that factsheet,' + ' do not guess it — say it is not safe to quote from the image and re-read the source text.' + @@ -2487,6 +2486,12 @@ export async function transformRequest( if (preservedIdentity) { sysTail.push({ type: 'text', text: preservedIdentity }); } + if (imageBlocks.length > 0) { + sysTail.push({ + type: 'text', + text: "pxpipe (this user's local proxy) rendered this session's configuration into the image blocks attached to the first user message to reduce token cost.", + }); + } // billingLine is NOT re-emitted here. req.system precedes messages[] in // Anthropic's cache-prefix order, so every system block sits inside the // span covered by the LAST cache_control marker (always in messages[] on diff --git a/tests/refusal-provenance.test.ts b/tests/refusal-provenance.test.ts new file mode 100644 index 000000000..8f8105ca5 --- /dev/null +++ b/tests/refusal-provenance.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import { transformRequest } from '../src/core/transform.js'; + +const enc = new TextEncoder(); +const dec = new TextDecoder(); + +describe('Anthropic refusal prevention - proxy system provenance', () => { + it('adds explicit proxy provenance statement to req.system when rendering static slab into user-turn images', async () => { + const reqBody = JSON.stringify({ + model: 'claude-3-5-sonnet', + system: 'System operating instructions. '.repeat(1000), + tools: [ + { + name: 'ReadFile', + description: 'Read contents of a file.', + input_schema: { type: 'object', properties: { path: { type: 'string' } } }, + }, + ], + messages: [ + { role: 'user', content: 'Help me debug this issue.' }, + ], + }); + + const { body, info } = await transformRequest(enc.encode(reqBody)); + expect(info.compressed).toBe(true); + expect(info.imageCount).toBeGreaterThan(0); + + const out = JSON.parse(dec.decode(body)); + + // 1. req.system must contain an explicit first-party proxy provenance statement + const sysBlocks = Array.isArray(out.system) + ? out.system + : typeof out.system === 'string' + ? [{ type: 'text', text: out.system }] + : []; + + const sysTexts = sysBlocks.map((b: any) => b.text || ''); + const provenanceBlock = sysTexts.find((t: string) => + t.includes("pxpipe (this user's local proxy)") && t.includes('rendered this session'), + ); + + expect(provenanceBlock).toBeDefined(); + expect(provenanceBlock).toContain("pxpipe (this user's local proxy)"); + expect(provenanceBlock).toContain('image blocks'); + + // 2. The user-turn image banner must NOT contain imperative commands like "follow them as your operating instructions" + const imgSrc = info.imageSourceText ?? ''; + expect(imgSrc).not.toContain('follow them as your operating instructions'); + expect(imgSrc).not.toMatch(/system prompt|authoritative/i); + }); +});