From bb9c7aa02dc89e7d20cc84b1191d2cdc015dd765 Mon Sep 17 00:00:00 2001 From: DJP Date: Tue, 12 May 2026 21:45:19 -0400 Subject: [PATCH] Box inbound: defensive loop guard on parent folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reject inbound events whose Box file isn't a direct child of the configured BOX_WATCH_FOLDER_ID, and explicitly skip any file whose parent IS BOX_OUT_FOLDER_ID. Box webhooks are folder-scoped already so a normal subscription won't fire on our own outbound writes — this is defence in depth in case a future webhook is mis-subscribed or the two folder ids ever drift into the same value. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/box/box-inbound-service.ts | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/lib/services/box/box-inbound-service.ts b/src/lib/services/box/box-inbound-service.ts index 37caff7..78a77e5 100644 --- a/src/lib/services/box/box-inbound-service.ts +++ b/src/lib/services/box/box-inbound-service.ts @@ -6,10 +6,42 @@ import { ingestInboundFile, type IngestResult } from "@/lib/services/inbound-ing * the file's name from Box (so the matcher has something to parse) and * delegates everything else — regex resolution, project/deliverable * matching, holding-pen vs active branching, notifications. + * + * Loop guard: only accepts events whose file's parent folder matches + * BOX_WATCH_FOLDER_ID. Box webhooks are already folder-scoped on + * subscription, but a misconfigured subscription on the outbound folder + * would otherwise ingest our own uploads. Defence in depth. */ export async function ingestBoxUpload(fileId: string): Promise { try { const meta = await getBoxFileMetadata(fileId); + + const watchFolderId = process.env.BOX_WATCH_FOLDER_ID; + const outFolderId = process.env.BOX_OUT_FOLDER_ID; + const parentId = meta.parent?.id; + + // Reject anything from the outbound folder — that's our own writes. + if (outFolderId && parentId === outFolderId) { + return { + ok: true, + status: "UNMATCHED", + error: `Ignoring event from outbound folder ${outFolderId} (loop guard).`, + }; + } + + // When the watch folder is configured, require the file to be a direct + // child of it. (We don't walk up the parent chain — files dropped + // into nested sub-folders won't fire matching webhooks anyway because + // Box only fires on the subscribed folder's direct contents unless + // the subscription includes descendants.) + if (watchFolderId && parentId !== watchFolderId) { + return { + ok: true, + status: "UNMATCHED", + error: `File parent ${parentId} is not the watch folder ${watchFolderId}.`, + }; + } + return ingestInboundFile({ source: "BOX", fileName: meta.name,