diff --git a/packages/plugins/sandbox-providers/kubernetes/src/upload-interceptor.ts b/packages/plugins/sandbox-providers/kubernetes/src/upload-interceptor.ts index 8e567d0a..54b657c5 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/upload-interceptor.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/upload-interceptor.ts @@ -123,6 +123,15 @@ export class FastUploadInterceptor { }; } + const activeUpload = this.findActiveUploadForCommand(command); + if (activeUpload) { + this.buffers.delete(activeUpload.tempPath); + return { + action: "error", + message: `Fast upload protocol violation for ${activeUpload.upload.targetPath}; retry the upload from the beginning.`, + }; + } + return { action: "passthrough", reason: "no upload pattern" }; } @@ -133,4 +142,13 @@ export class FastUploadInterceptor { get pendingCount(): number { return this.buffers.size; } + + private findActiveUploadForCommand(command: string): { tempPath: string; upload: BufferedUpload } | null { + for (const [tempPath, upload] of this.buffers) { + if (command.includes(`'${tempPath}'`)) { + return { tempPath, upload }; + } + } + return null; + } } diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/upload-interceptor.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/upload-interceptor.test.ts index dcfc2ec9..e93ff3fb 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/upload-interceptor.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/upload-interceptor.test.ts @@ -46,7 +46,7 @@ describe("FastUploadInterceptor", () => { ).toMatchObject({ action: "passthrough", reason: "finalize without buffered state" }); }); - it("does not intercept chunks with padding before the end", () => { + it("fails fast when an unrecognized command targets an active upload", () => { const interceptor = new FastUploadInterceptor(); const target = "/workspace/file.bin"; @@ -56,10 +56,12 @@ describe("FastUploadInterceptor", () => { ), ).toMatchObject({ action: "ack" }); - expect( - interceptor.decide(`printf '%s' 'aGVs=bG8=' >> '${target}.paperclip-upload.b64'`), - ).toMatchObject({ action: "passthrough", reason: "no upload pattern" }); - expect(interceptor.pendingCount).toBe(1); + const decision = interceptor.decide(`printf '%s' 'aGVs=bG8=' >> '${target}.paperclip-upload.b64'`); + expect(decision).toMatchObject({ + action: "error", + message: expect.stringContaining("Fast upload protocol violation"), + }); + expect(interceptor.pendingCount).toBe(0); }); it("fails fast when data arrives after a padded chunk", () => {