mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(authz): say every plugin management step is board-only in plugin denials
An agent asked to finish configuring a plugin first read the plugin list. The board-only denial said that this action was final. The agent then spent 110 seconds searching the API catalog for an agent route to the configuration and secret binding, and its reply came after the chat window closed. A board-only denial on any /api/plugins route now also says that listing, installing, configuring (including choosing a secret for a plugin setting), enabling, disabling, upgrading and removing plugins have no agent route. Other board-only denials are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f9aa43e291
commit
3c0abb7753
2 files changed
+25
-2
No files matched your search
@@ -58,6 +58,19 @@ describe("board-only denial", () => {
|
||||
expect(res.body.remediation).toContain(link);
|
||||
});
|
||||
|
||||
it("tells an agent denied any plugin route that every plugin management step is board-only", async () => {
|
||||
// An agent asked to finish configuring a plugin usually reads the plugin list first.
|
||||
// That denial must also settle configuration and secret binding, or the agent keeps
|
||||
// searching the catalog for an agent route to them.
|
||||
for (const path of ["/api/plugins", `/api/plugins/${AGENT_ID}/config`]) {
|
||||
const res = await request(createApp(agentActor)).get(path).expect(403);
|
||||
expect(res.body.remediation).toContain("Plugin management is board-only throughout");
|
||||
expect(res.body.remediation).toContain("configuring (including choosing a secret for a plugin setting)");
|
||||
}
|
||||
const other = await request(createApp(agentActor)).post(`/api/companies/${COMPANY_ID}/secrets`).expect(403);
|
||||
expect(other.body.remediation).not.toContain("Plugin management");
|
||||
});
|
||||
|
||||
it("links the resolved project when an agent names the project by its short reference", async () => {
|
||||
const app = express();
|
||||
app.use((req, _res, next) => {
|
||||
|
||||
@@ -81,8 +81,17 @@ function pluginControlPage(path: string): [string, string] | null {
|
||||
: ["plugin settings", "/<prefix>/company/settings/instance/plugins"];
|
||||
}
|
||||
|
||||
// Every plugin management route is board-only, so a denial on any one of them
|
||||
// (often the list an agent reads first) also answers the plugin's other management steps.
|
||||
const PLUGIN_ROUTE = /^\/plugins(?:\/|$)/;
|
||||
const PLUGIN_ADMINISTRATION_SCOPE = "Plugin management is board-only throughout: listing, installing, configuring (including choosing a secret for a plugin setting), enabling, disabling, upgrading and removing plugins have no agent route either.";
|
||||
|
||||
function deniedApiPath(req: Request) {
|
||||
return (req.originalUrl ?? "").split("?")[0]!.replace(/^\/api(?=\/)/, "");
|
||||
}
|
||||
|
||||
function boardControlPage(req: Request) {
|
||||
const path = (req.originalUrl ?? "").split("?")[0]!.replace(/^\/api(?=\/)/, "");
|
||||
const path = deniedApiPath(req);
|
||||
const pluginPage = pluginControlPage(path);
|
||||
if (pluginPage) return pluginPage;
|
||||
for (const [pattern, page] of BOARD_CONTROL_PAGES) {
|
||||
@@ -99,12 +108,13 @@ function boardControlPage(req: Request) {
|
||||
export function boardAccessRequired(req: Request) {
|
||||
if (req.actor.type !== "agent") return forbidden("Board access required");
|
||||
const page = boardControlPage(req);
|
||||
const scope = PLUGIN_ROUTE.test(deniedApiPath(req)) ? `${PLUGIN_ADMINISTRATION_SCOPE} ` : "";
|
||||
const where = page
|
||||
? `They can do it in the Paperclip web app: link [Open ${page[0]}](${page[1]}), with this conversation's company prefix as <prefix>.`
|
||||
: "They can do it in the Paperclip web app or with their own board credentials. Link a page only if you already know its route; do not search for one.";
|
||||
return forbidden("Board access required", {
|
||||
code: BOARD_ACCESS_REQUIRED_CODE,
|
||||
remediation: `This action is board-only by design and this denial is final: no agent credential, tool or other API route can do it, so do not search for one. Stop working on this step and reply now: tell the person that a board user must do it. ${where}`,
|
||||
remediation: `This action is board-only by design and this denial is final: no agent credential, tool or other API route can do it, so do not search for one. Stop working on this step and reply now: tell the person that a board user must do it. ${scope}${where}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user