forked from farhoodlabs/paperclip
fix(plugin): harden kubernetes exec upload parsing
This commit is contained in:
@@ -59,6 +59,8 @@ export async function execInPod(
|
|||||||
stderrStream.on("data", (chunk: Buffer) => {
|
stderrStream.on("data", (chunk: Buffer) => {
|
||||||
stderrData += chunk.toString("utf-8");
|
stderrData += chunk.toString("utf-8");
|
||||||
});
|
});
|
||||||
|
stdoutStream.on("error", () => {});
|
||||||
|
stderrStream.on("error", () => {});
|
||||||
|
|
||||||
return await new Promise<ExecInPodResult>(
|
return await new Promise<ExecInPodResult>(
|
||||||
(resolve, reject) => {
|
(resolve, reject) => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { posix as pathPosix } from "node:path";
|
|||||||
const INIT_RE =
|
const INIT_RE =
|
||||||
/^mkdir -p '([^']+)' && rm -f '([^']+)\.paperclip-upload\.b64' && : > '\2\.paperclip-upload\.b64'$/;
|
/^mkdir -p '([^']+)' && rm -f '([^']+)\.paperclip-upload\.b64' && : > '\2\.paperclip-upload\.b64'$/;
|
||||||
const CHUNK_RE =
|
const CHUNK_RE =
|
||||||
/^printf '%s' '([A-Za-z0-9+/=]+)' >> '([^']+)\.paperclip-upload\.b64'$/;
|
/^printf '%s' '([A-Za-z0-9+/]+={0,2})' >> '([^']+)\.paperclip-upload\.b64'$/;
|
||||||
const FINALIZE_RE =
|
const FINALIZE_RE =
|
||||||
/^base64 -d < '([^']+)\.paperclip-upload\.b64' > '\1' && rm -f '\1\.paperclip-upload\.b64'$/;
|
/^base64 -d < '([^']+)\.paperclip-upload\.b64' > '\1' && rm -f '\1\.paperclip-upload\.b64'$/;
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ describe("execInPod", () => {
|
|||||||
expect(result).toEqual({ exitCode: 0, timedOut: false, stdout: "ok\n", stderr: "" });
|
expect(result).toEqual({ exitCode: 0, timedOut: false, stdout: "ok\n", stderr: "" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles output stream errors after status completion", async () => {
|
||||||
|
execMock.mockImplementation((_namespace, _pod, _container, _command, stdout, _stderr, _stdin, _tty, statusCallback) => {
|
||||||
|
statusCallback({ status: "Success" });
|
||||||
|
stdout.emit("error", new Error("write after end"));
|
||||||
|
return Promise.resolve(new EventEmitter());
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await execInPod({} as never, "ns", "pod-1", "agent", ["echo", "ok"]);
|
||||||
|
expect(result).toEqual({ exitCode: 0, timedOut: false, stdout: "", stderr: "" });
|
||||||
|
});
|
||||||
|
|
||||||
it("returns an execution failure if the websocket closes before a status frame", async () => {
|
it("returns an execution failure if the websocket closes before a status frame", async () => {
|
||||||
const ws = new EventEmitter();
|
const ws = new EventEmitter();
|
||||||
execMock.mockResolvedValue(ws);
|
execMock.mockResolvedValue(ws);
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ describe("FastUploadInterceptor", () => {
|
|||||||
).toMatchObject({ action: "passthrough", reason: "finalize without buffered state" });
|
).toMatchObject({ action: "passthrough", reason: "finalize without buffered state" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not intercept chunks with padding before the end", () => {
|
||||||
|
const interceptor = new FastUploadInterceptor();
|
||||||
|
const target = "/workspace/file.bin";
|
||||||
|
|
||||||
|
expect(
|
||||||
|
interceptor.decide(
|
||||||
|
`mkdir -p '/workspace' && rm -f '${target}.paperclip-upload.b64' && : > '${target}.paperclip-upload.b64'`,
|
||||||
|
),
|
||||||
|
).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);
|
||||||
|
});
|
||||||
|
|
||||||
it("falls through when the init command does not match the target parent directory", () => {
|
it("falls through when the init command does not match the target parent directory", () => {
|
||||||
const interceptor = new FastUploadInterceptor();
|
const interceptor = new FastUploadInterceptor();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user