Public: anyone can read this; the crew can change it.
A test pins MCP `ssh_keys`: list, add and remove only your own keys, an `rw` token to change, an unknown action refused #7155
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7146-a-test-pins-mcp-ssh-keys-list-add-and-re → main 2 commits fork 2 threads · 2 unresolved
1 file
changed +29
−1
test/mcp-ssh-keys.test.ts +29 −1
| @@ -7,8 +7,9 @@ import { mkdtemp, rm } from "node:fs/promises"; | ||
| 7 | 7 | import { tmpdir } from "node:os"; |
| 8 | 8 | import { join } from "node:path"; |
| 9 | 9 | import { createApp, type App } from "../src/app.ts"; |
| 10 | import { listKeys } from "../src/core/keys.ts"; | |
| 10 | 11 | import { createToken } from "../src/core/tokens.ts"; |
| 11 | import { createUser } from "../src/core/users.ts"; | |
| 12 | import { createUser, getUserByName } from "../src/core/users.ts"; | |
| 12 | 13 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 14 | |
| 14 | 15 | const KEY_A = |
| @@ -104,4 +105,31 @@ describe("MCP ssh_keys", () => { | ||
| 104 | 105 | expect((await tool(bobRw, { action: "list" })).value).toEqual([]); |
| 105 | 106 | expect((await tool(aliceRw, { action: "list" })).value).toHaveLength(1); |
| 106 | 107 | }); |
| 108 | ||
| 109 | test("a read-only token's refusal stores nothing and names forbidden", async () => { | |
| 110 | const before = (await tool(aliceRw, { action: "list" })).value; | |
| 111 | const refused = await tool(aliceRo, { action: "add", key: KEY_A }); | |
| 112 | expect(refused.error).toStartWith("forbidden"); | |
| 113 | expect((await tool(aliceRw, { action: "list" })).value).toEqual(before); | |
| 114 | expect((await tool(bobRw, { action: "list" })).value).toEqual([]); | |
| 115 | }); | |
| 116 | ||
| 117 | test("another account's key id is not_found and the key stays in the database", async () => { | |
| 118 | const [mine] = (await tool(aliceRw, { action: "list" })).value; | |
| 119 | expect((await tool(bobRw, { action: "remove", id: mine.id })).error).toStartWith("not_found"); | |
| 120 | const alice = getUserByName(app.db, "alice")!; | |
| 121 | expect(listKeys(app.db, alice.id).map((k) => k.id)).toContain(mine.id); | |
| 122 | }); | |
| 123 | ||
| 124 | test("an unknown action is invalid_action", async () => { | |
| 125 | expect((await tool(aliceRw, { action: "rotate" })).error).toStartWith("invalid_action"); | |
| 126 | }); | |
| 127 | ||
| 128 | test("malformed key lines are invalid_key and store nothing", async () => { | |
| 129 | const before = (await tool(bobRw, { action: "list" })).value; | |
| 130 | for (const key of ["not a key", "ssh-ed25519 !!!notbase64", ""]) { | |
| 131 | expect((await tool(bobRw, { action: "add", key })).error).toStartWith("invalid_key"); | |
| 132 | } | |
| 133 | expect((await tool(bobRw, { action: "list" })).value).toEqual(before); | |
| 134 | }); | |
| 107 | 135 | }); |
new above
offline