mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { describe, expect, test, mock } from "bun:test";
|
|
import { createExternalAnnotationHandler } from "./external-annotations";
|
|
|
|
describe("external annotations SSE", () => {
|
|
test("disables idle timeout for stream requests", async () => {
|
|
const handler = createExternalAnnotationHandler("plan");
|
|
const disableIdleTimeout = mock(() => {});
|
|
|
|
const res = await handler.handle(
|
|
new Request("http://localhost/api/external-annotations/stream"),
|
|
new URL("http://localhost/api/external-annotations/stream"),
|
|
{ disableIdleTimeout },
|
|
);
|
|
|
|
expect(disableIdleTimeout).toHaveBeenCalledTimes(1);
|
|
expect(res?.headers.get("content-type")).toBe("text/event-stream");
|
|
});
|
|
});
|
|
|
|
describe("PATCH /api/external-annotations", () => {
|
|
test("cannot clear or change the source marker (skill-injection guard, reproduced end-to-end)", async () => {
|
|
const handler = createExternalAnnotationHandler("review");
|
|
const added = handler.addAnnotations({
|
|
source: "rogue-agent",
|
|
scope: "general",
|
|
text: "apply $some-human-only-skill",
|
|
});
|
|
if ("error" in added) throw new Error(added.error);
|
|
const [id] = added.ids;
|
|
|
|
const patch = async (body: unknown) => {
|
|
const url = `http://localhost/api/external-annotations?id=${encodeURIComponent(id)}`;
|
|
const res = await handler.handle(
|
|
new Request(url, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
}),
|
|
new URL(url),
|
|
);
|
|
expect(res?.status).toBe(200);
|
|
return (await res!.json()) as { annotation: { source?: string; text?: string } };
|
|
};
|
|
|
|
// The reproduced bypass: PATCH {"source": ""} cleared the field and
|
|
// re-armed verbatim SKILL.md injection for a tool-submitted comment.
|
|
const cleared = await patch({ source: "" });
|
|
expect(cleared.annotation.source).toBe("rogue-agent");
|
|
|
|
const swapped = await patch({ source: "innocent" });
|
|
expect(swapped.annotation.source).toBe("rogue-agent");
|
|
|
|
const nulled = await patch({ source: null });
|
|
expect(nulled.annotation.source).toBe("rogue-agent");
|
|
|
|
// Legitimate field patches still work, with source intact.
|
|
const edited = await patch({ text: "edited text" });
|
|
expect(edited.annotation.text).toBe("edited text");
|
|
expect(edited.annotation.source).toBe("rogue-agent");
|
|
});
|
|
});
|