diff --git a/packages/api/src/shared-links/protection.spec.ts b/packages/api/src/shared-links/protection.spec.ts index 5db5222671..6dc33f4ccd 100644 --- a/packages/api/src/shared-links/protection.spec.ts +++ b/packages/api/src/shared-links/protection.spec.ts @@ -396,6 +396,124 @@ describe('shared file metadata protection', () => { }, ); + it.each([ + [ + 'file status', + fileContentFilters, + { + isCreatedByUser: true, + files: [{ status: 'PRIVATE-SENTINEL' }], + }, + 'file', + 'content', + ], + [ + 'attachment preview error', + attachmentFilters, + { + isCreatedByUser: true, + attachments: [{ previewError: 'PRIVATE-SENTINEL' }], + }, + 'message', + 'attachment_reference', + ], + [ + 'tool call identifier', + toolOutputFilters, + { + isCreatedByUser: false, + attachments: [{ toolCallId: 'PRIVATE-SENTINEL' }], + }, + 'tool_argument', + 'output', + ], + ])( + 'checks a standard serialized %s under its semantic provenance', + (_label, filters, message, source, field) => { + const error = capturePolicyError(() => + assertSharedFileMetadataAllowed({ + filters, + messages: [message], + shareId: 'share-123', + }), + ); + + expect(error).toBeInstanceOf(ContentFilterError); + expect(error.body).toMatchObject({ + error: 'content_filter_block', + source, + field, + }); + expect(JSON.stringify(error.body)).not.toContain('PRIVATE-SENTINEL'); + }, + ); + + it('keeps a user-submitted toolCallId under message provenance', () => { + const error = capturePolicyError(() => + assertSharedFileMetadataAllowed({ + filters: attachmentFilters, + messages: [ + { + isCreatedByUser: true, + attachments: [{ toolCallId: 'PRIVATE-SENTINEL' }], + }, + ], + shareId: 'share-123', + }), + ); + + expect(error).toBeInstanceOf(ContentFilterError); + expect(error.body).toMatchObject({ + error: 'content_filter_block', + source: 'message', + field: 'attachment_reference', + }); + }); + + it('preserves field granularity for standard serialized strings', () => { + expect(() => + assertSharedFileMetadataAllowed({ + filters: { + files: { + pii: { + fields: ['name'], + starterPatterns: [], + customPatterns: [BLOCK_PATTERN], + }, + }, + }, + messages: [ + { + isCreatedByUser: true, + files: [{ status: 'PRIVATE-SENTINEL' }], + }, + ], + shareId: 'share-123', + }), + ).not.toThrow(); + }); + + it('keeps standard serialized string protection default-off', () => { + expect(() => + assertSharedFileMetadataAllowed({ + filters: {}, + messages: [ + { + isCreatedByUser: true, + files: [{ status: 'PRIVATE-SENTINEL' }], + attachments: [ + { + previewError: 'PRIVATE-SENTINEL', + toolCallId: 'PRIVATE-SENTINEL', + }, + ], + }, + ], + shareId: 'share-123', + }), + ).not.toThrow(); + }); + it.each([ [ 'web-search result', diff --git a/packages/api/src/shared-links/protection.ts b/packages/api/src/shared-links/protection.ts index 58984f26e2..bd6636f33f 100644 --- a/packages/api/src/shared-links/protection.ts +++ b/packages/api/src/shared-links/protection.ts @@ -868,7 +868,7 @@ function extractNestedSerializedPayloadFragments( if (typeof value === 'string') { if (SERIALIZED_LOCATOR_KEYS.includes(key as (typeof SERIALIZED_LOCATOR_KEYS)[number])) { continue; - } else if (activeClassifications.length > 0 && FILE_FIELD_BY_STANDARD_KEY.has(key)) { + } else if (activeClassifications.length > 0) { for (const classification of activeClassifications) { inspectSerializedString( state,