authorgravatar for mohammedshazeb10@gmail.commohammed shazeb <mohammedshazeb10@gmail.com> 2026-08-15 17:31:26+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-15 12:01:26+00:00
log34a12bc33f5fba02b0b398e7dff35ca041a87dc7
treea85b50df50b71d33436b37acef554815dd2e254f
parent7afa184a99b266d466cc9517c147a75c3d839ad7
signature Signed by PGP key B5690EEEBB952194

fix(web): reject unsupported composer image types at attach time (#6574)


5 files changed, 39 insertions(+), 0 deletions(-)

apps/mobile/src/features/sharing/incoming-share-model.ts+8
......@@ -1,4 +1,5 @@
11import {
2 isProviderSendTurnSupportedImageMimeType,
23 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
34 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
45} from "@t3tools/contracts";
......@@ -162,6 +163,13 @@ export async function buildIncomingShareDraft(input: {
162163 await releaseOwnedFiles(input.fileReader, [uri, payload.value]);
163164 continue;
164165 }
166 if (!isProviderSendTurnSupportedImageMimeType(mimeType)) {
167 warnings.push(
168 `'${resolved?.originalName ?? fallbackName(uri, index, mimeType)}' is not a supported image type.`,
169 );
170 await releaseOwnedFiles(input.fileReader, [uri, payload.value]);
171 continue;
172 }
165173 if (
166174 resolved?.contentSize !== null &&
167175 resolved?.contentSize !== undefined &&
apps/mobile/src/lib/composerImages.ts+5
......@@ -1,4 +1,5 @@
11import {
2 isProviderSendTurnSupportedImageMimeType,
23 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
34 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
45 type UploadChatImageAttachment,
......@@ -98,6 +99,10 @@ export async function pickComposerImages(input: { readonly existingCount: number
9899 error = `Unsupported file type for '${asset.fileName ?? "image"}'.`;
99100 continue;
100101 }
102 if (!isProviderSendTurnSupportedImageMimeType(mimeType)) {
103 error = `'${asset.fileName ?? "image"}' is not a supported image type. Attach GIF, JPEG, PNG, or WebP images.`;
104 continue;
105 }
101106
102107 const base64 = asset.base64;
103108 if (!base64) {
apps/web/src/components/chat/ChatComposer.tsx+5
......@@ -12,6 +12,7 @@ import type {
1212 ThreadId,
1313} from "@t3tools/contracts";
1414import {
15 isProviderSendTurnSupportedImageMimeType,
1516 ProviderDriverKind,
1617 ProviderInstanceId,
1718 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
......@@ -2305,6 +2306,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
23052306 error = `Unsupported file type for '${file.name}'. Please attach image files only.`;
23062307 continue;
23072308 }
2309 if (!isProviderSendTurnSupportedImageMimeType(file.type)) {
2310 error = `'${file.name}' is not a supported image type. Attach GIF, JPEG, PNG, or WebP images.`;
2311 continue;
2312 }
23082313 if (reservedCount >= PROVIDER_SEND_TURN_MAX_ATTACHMENTS) {
23092314 error = `You can attach up to ${PROVIDER_SEND_TURN_MAX_ATTACHMENTS} images per message.`;
23102315 break;
packages/contracts/src/orchestration.test.ts+7
......@@ -23,6 +23,7 @@ import {
2323 ThreadCreatedPayload,
2424 ThreadTurnDiff,
2525 ThreadTurnStartRequestedPayload,
26 isProviderSendTurnSupportedImageMimeType,
2627} from "./orchestration.ts";
2728import { ProviderInstanceId } from "./providerInstance.ts";
2829
......@@ -935,3 +936,9 @@ it.effect("project favicon overrides accept only supported image files", () =>
935936 assert.strictEqual(invalid._tag, "Failure");
936937 }),
937938);
939
940it("isProviderSendTurnSupportedImageMimeType accepts raster formats and rejects svg", () => {
941 assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/png"), true);
942 assert.strictEqual(isProviderSendTurnSupportedImageMimeType("IMAGE/JPEG"), true);
943 assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/svg+xml"), false);
944});
packages/contracts/src/orchestration.ts+14
......@@ -144,6 +144,20 @@ export type ProviderUserInputAnswers = typeof ProviderUserInputAnswers.Type;
144144export const PROVIDER_SEND_TURN_MAX_INPUT_CHARS = 120_000;
145145export const PROVIDER_SEND_TURN_MAX_ATTACHMENTS = 8;
146146export const PROVIDER_SEND_TURN_MAX_IMAGE_BYTES = 10 * 1024 * 1024;
147export const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES = [
148 "image/gif",
149 "image/jpeg",
150 "image/png",
151 "image/webp",
152] as const;
153const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET = new Set<string>(
154 PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES,
155);
156
157/** Whether a pasted or picked image mime type can be sent on a provider turn. */
158export function isProviderSendTurnSupportedImageMimeType(mimeType: string): boolean {
159 return PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET.has(mimeType.toLowerCase());
160}
147161const PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS = 14_000_000;
148162const CHAT_ATTACHMENT_ID_MAX_CHARS = 128;
149163// Correlation id is command id by design in this model.