authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 20:48:48-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-27 21:30:46-08:00
log6f92f0a66855ef6745814b0a91cffc2d588e0777
tree4401cddfa575cdc34f5baaaa7785229bb11d13b9
parent35817601ff3f3d4f2e27ea8c5c8a08a8b0926dc3

kewl


2 files changed, 21 insertions(+), 29 deletions(-)

apps/server/src/orchestration/Layers/ProjectionPipeline.ts+20-26
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import Mime from "@effect/platform-node/Mime";
1import {2import {
2 ApprovalRequestId,3 ApprovalRequestId,
3 type ChatAttachment,4 type ChatAttachment,
...@@ -112,20 +113,13 @@ function parseBase64DataUrl(...@@ -112,20 +113,13 @@ function parseBase64DataUrl(
112}113}
113114
114function inferImageExtension(attachment: Extract<ChatAttachment, { type: "image" }>): string {115function inferImageExtension(attachment: Extract<ChatAttachment, { type: "image" }>): string {
115 const normalizedMimeType = attachment.mimeType.trim().toLowerCase();116 const normalizedMimeType = attachment.mimeType.toLowerCase();
116 if (normalizedMimeType.startsWith("image/")) {117 if (normalizedMimeType.startsWith("image/")) {
117 const fromMime = IMAGE_EXTENSION_BY_MIME_TYPE[normalizedMimeType];118 const fromMime = IMAGE_EXTENSION_BY_MIME_TYPE[normalizedMimeType];
118 if (fromMime) {119 if (fromMime) return fromMime;
119 return fromMime;
120 }
121 }
122
123 const fromName = attachment.name.includes(".")
124 ? `.${attachment.name.split(".").slice(-1)[0]?.toLowerCase() ?? ""}`
125 : "";
126 if (/^\.[a-z0-9]{1,12}$/.test(fromName) && SAFE_IMAGE_FILE_EXTENSIONS.has(fromName)) {
127 return fromName;
128 }120 }
121 const ext = Mime.getExtension(attachment.mimeType);
122 if (ext && SAFE_IMAGE_FILE_EXTENSIONS.has(ext)) return ext;
129 return ".bin";123 return ".bin";
130}124}
131125
...@@ -135,14 +129,10 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {...@@ -135,14 +129,10 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {
135 readonly attachments: ReadonlyArray<ChatAttachment>;129 readonly attachments: ReadonlyArray<ChatAttachment>;
136 readonly stageFileWrite: (pendingWrite: PendingAttachmentWrite) => void;130 readonly stageFileWrite: (pendingWrite: PendingAttachmentWrite) => void;
137}) {131}) {
138 if (input.attachments.length === 0) {132 if (input.attachments.length === 0) return [];
139 return [] as ReadonlyArray<ChatAttachment>;
140 }
141133
142 const serverConfig = yield* Effect.serviceOption(ServerConfig);134 const serverConfig = yield* Effect.serviceOption(ServerConfig);
143 if (Option.isNone(serverConfig)) {135 if (Option.isNone(serverConfig)) return input.attachments;
144 return input.attachments;
145 }
146136
147 const path = yield* Path.Path;137 const path = yield* Path.Path;
148 const attachmentsRootDir = path.join(serverConfig.value.stateDir, ATTACHMENTS_STATE_SUBDIRECTORY);138 const attachmentsRootDir = path.join(serverConfig.value.stateDir, ATTACHMENTS_STATE_SUBDIRECTORY);
...@@ -166,9 +156,7 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {...@@ -166,9 +156,7 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: {
166 }156 }
167157
168 const bytes = Buffer.from(parsed.base64, "base64");158 const bytes = Buffer.from(parsed.base64, "base64");
169 if (bytes.byteLength === 0) {159 if (bytes.byteLength === 0) return attachment;
170 return attachment;
171 }
172160
173 const fileName = `${index}${inferImageExtension({161 const fileName = `${index}${inferImageExtension({
174 ...attachment,162 ...attachment,
...@@ -375,9 +363,7 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {...@@ -375,9 +363,7 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {
375 entries,363 entries,
376 (entry) =>364 (entry) =>
377 Effect.gen(function* () {365 Effect.gen(function* () {
378 const threadRelativePath = entry366 const threadRelativePath = entry.replace(/^[/\\]+/, "").replace(/\\/g, "/");
379 .replace(/^[/\\]+/, "")
380 .replace(/\\/g, "/");
381 if (threadRelativePath.length === 0 || threadRelativePath.startsWith("..")) {367 if (threadRelativePath.length === 0 || threadRelativePath.startsWith("..")) {
382 return;368 return;
383 }369 }
...@@ -411,7 +397,9 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {...@@ -411,7 +397,9 @@ const runAttachmentSideEffects = Effect.fn(function* (input: {
411 writesByPath.entries(),397 writesByPath.entries(),
412 ([absolutePath, bytes]) =>398 ([absolutePath, bytes]) =>
413 Effect.gen(function* () {399 Effect.gen(function* () {
414 yield* input.fileSystem.makeDirectory(input.path.dirname(absolutePath), { recursive: true });400 yield* input.fileSystem.makeDirectory(input.path.dirname(absolutePath), {
401 recursive: true,
402 });
415 const alreadyExists = yield* input.fileSystem403 const alreadyExists = yield* input.fileSystem
416 .exists(absolutePath)404 .exists(absolutePath)
417 .pipe(Effect.catch(() => Effect.succeed(false)));405 .pipe(Effect.catch(() => Effect.succeed(false)));
...@@ -615,7 +603,10 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {...@@ -615,7 +603,10 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {
615 }603 }
616 });604 });
617605
618 const applyThreadMessagesProjection: ProjectorDefinition["apply"] = (event, attachmentSideEffects) =>606 const applyThreadMessagesProjection: ProjectorDefinition["apply"] = (
607 event,
608 attachmentSideEffects,
609 ) =>
619 Effect.gen(function* () {610 Effect.gen(function* () {
620 switch (event.type) {611 switch (event.type) {
621 case "thread.message-sent": {612 case "thread.message-sent": {
...@@ -778,7 +769,10 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {...@@ -778,7 +769,10 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {
778 });769 });
779 });770 });
780771
781 const applyThreadTurnsProjection: ProjectorDefinition["apply"] = (event, _attachmentSideEffects) =>772 const applyThreadTurnsProjection: ProjectorDefinition["apply"] = (
773 event,
774 _attachmentSideEffects,
775 ) =>
782 Effect.gen(function* () {776 Effect.gen(function* () {
783 switch (event.type) {777 switch (event.type) {
784 case "thread.turn-start-requested": {778 case "thread.turn-start-requested": {
apps/server/src/wsServer.ts+1-3
...@@ -330,9 +330,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<...@@ -330,9 +330,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
330 const staticRequestPath = url.pathname === "/" ? "/index.html" : url.pathname;330 const staticRequestPath = url.pathname === "/" ? "/index.html" : url.pathname;
331 const rawStaticRelativePath = staticRequestPath.replace(/^[/\\]+/, "");331 const rawStaticRelativePath = staticRequestPath.replace(/^[/\\]+/, "");
332 const hasRawLeadingParentSegment = rawStaticRelativePath.startsWith("..");332 const hasRawLeadingParentSegment = rawStaticRelativePath.startsWith("..");
333 const staticRelativePath = path333 const staticRelativePath = path.normalize(rawStaticRelativePath).replace(/^[/\\]+/, "");
334 .normalize(rawStaticRelativePath)
335 .replace(/^[/\\]+/, "");
336 const hasPathTraversalSegment = staticRelativePath.startsWith("..");334 const hasPathTraversalSegment = staticRelativePath.startsWith("..");
337 if (335 if (
338 staticRelativePath.length === 0 ||336 staticRelativePath.length === 0 ||