@@ -1331,9 +1331,9 @@ describe("ClaudeAdapterV2 MCP elicitation acceptance", () => {
13311331 );
13321332 });
13331333
1334- it("has no answer for URL mode or a form asking for a value", () => {
1334+ it("has no answer for unsafe URLs or a form asking for a value", () => {
13351335 for (const request of [
1336- { serverName: "deploys", message: "Sign in", mode: "url", url: "https://example.com " },
1336+ { serverName: "deploys", message: "Sign in", mode: "url", url: "javascript:alert(1) " },
13371337 form({ reason: { type: "string" } }, ["reason"]),
13381338 form({ grant: { type: "string", enum: ["always", "session"] } }, ["grant"]),
13391339 form({ grant: { type: "string", enum: ["deny", "always"], default: "always" } }, ["grant"]),
@@ -5718,6 +5718,34 @@ describe("ClaudeAdapterV2 background wake turns", () => {
57185718 }).pipe(Effect.scoped, Effect.provide(testLayer)),
57195719 );
57205720
5721+ it.effect.each(["accept", "decline", "cancel", "acceptAlways", "acceptForSession"] as const)(
5722+ "shows a URL card and responds to %s without form content",
5723+ (decision) =>
5724+ Effect.gen(function* () {
5725+ const { harness, elicit, awaitCard } = yield* startElicitationTurn();
5726+ const url = "https://example.com/authorize?transfer=one-time";
5727+ const answer = yield* elicit({ ...approvalOnly, mode: "url", url });
5728+ const request = yield* awaitCard(1);
5729+ const card = harness.events.findLast(
5730+ (event) =>
5731+ event.type === "turn_item.updated" && event.turnItem.type === "approval_request",
5732+ );
5733+ assert.deepInclude(card?.type === "turn_item.updated" ? card.turnItem : undefined, {
5734+ elicitationUrl: url,
5735+ options: [
5736+ { decision: "cancel", label: "Cancel" },
5737+ { decision: "decline", label: "Decline" },
5738+ { decision: "accept", label: "Continue" },
5739+ ],
5740+ });
5741+ assert.isUndefined(answer.pollUnsafe());
5742+ yield* harness.runtime.respondToRuntimeRequest({ requestId: request.id, decision });
5743+ assert.deepEqual(yield* Fiber.join(answer), {
5744+ action: decision === "accept" || decision === "cancel" ? decision : "decline",
5745+ });
5746+ }).pipe(Effect.scoped, Effect.provide(testLayer)),
5747+ );
5748+
57215749 it.effect.each(["decline", "cancel"] as const)("answers %s with that action", (decision) =>
57225750 Effect.gen(function* () {
57235751 const { harness, elicit, awaitCard } = yield* startElicitationTurn();
@@ -5740,11 +5768,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
57405768 }).pipe(Effect.scoped, Effect.provide(testLayer)),
57415769 );
57425770
5743- it.effect("declines URL and value forms without a card", () =>
5771+ it.effect("declines unsafe URL and value forms without a card", () =>
57445772 Effect.gen(function* () {
57455773 const { elicit, runtimeRequests } = yield* startElicitationTurn();
57465774 for (const request of [
5747- { ...approvalOnly, mode: "url", url: "https ://example.com /auth" },
5775+ { ...approvalOnly, mode: "url", url: "file :///tmp /auth" },
57485776 {
57495777 ...approvalOnly,
57505778 requestedSchema: {
0 commit comments