fix: extend content delete authorization to allow administrators alongside owners across all endpoints

This commit is contained in:
2026-06-11 22:40:27 +00:00
parent 9a93debfc9
commit 1e2f304935
14 changed files with 164 additions and 61 deletions
+36 -16
View File
@@ -23,6 +23,20 @@ def upload(name: str, description: str) -> Param:
return Param(name=name, location="file", description=description, required=True)
def confirm() -> Param:
return Param(
name="confirm",
location="body",
description=(
"Set to true ONLY after the user has explicitly confirmed this irreversible action. "
"Leave it unset on the first call: the tool refuses and tells you to confirm with the "
"user, and only a repeat call with confirm=true actually performs it."
),
required=False,
type="boolean",
)
ATTACHMENTS = "Comma separated attachment uids returned by upload_file."
TARGET_TYPE = "Target type, e.g. post, comment, project, gist."
@@ -159,12 +173,13 @@ ACTIONS: tuple[Action, ...] = (
name="delete_post",
method="POST",
path="/posts/delete/{post_slug}",
summary="Delete a post",
summary="Delete a post (your own, or any post when you are an administrator). Soft delete, confirmation required",
params=(
path(
"post_slug",
"Exact post slug copied from a /posts/... link in a feed or listing response; do not build it from the title.",
),
confirm(),
),
),
Action(
@@ -185,8 +200,8 @@ ACTIONS: tuple[Action, ...] = (
name="delete_comment",
method="POST",
path="/comments/delete/{comment_uid}",
summary="Delete a comment",
params=(path("comment_uid", "Uid of the comment."),),
summary="Delete a comment (your own, or any comment when you are an administrator). Soft delete, confirmation required",
params=(path("comment_uid", "Uid of the comment."), confirm()),
),
Action(
name="list_projects",
@@ -254,12 +269,13 @@ ACTIONS: tuple[Action, ...] = (
name="delete_project",
method="POST",
path="/projects/delete/{project_slug}",
summary="Delete a project",
summary="Delete a project and all its files (your own, or any project when you are an administrator). Soft delete, confirmation required",
params=(
path(
"project_slug",
"Exact project slug copied from a /projects/... link in a listing response; do not build it from the title.",
),
confirm(),
),
),
Action(
@@ -475,10 +491,11 @@ ACTIONS: tuple[Action, ...] = (
name="project_delete_file",
method="POST",
path="/projects/{project_slug}/files/delete",
summary="Delete a file or directory (recursive) from a project filesystem",
summary="Delete a file or directory (recursive) from a project filesystem (project owner, or any project when you are an administrator). Soft delete, confirmation required",
params=(
path("project_slug", "Project slug or uid."),
body("path", "Relative path to delete.", required=True),
confirm(),
),
),
Action(
@@ -779,14 +796,14 @@ ACTIONS: tuple[Action, ...] = (
name="delete_media",
method="POST",
path="/media/{uid}/delete",
summary="Delete one of your uploaded media attachments",
summary="Delete an uploaded media attachment (your own, or anyone's when you are an administrator)",
description=(
"Removes the attachment from the user's Media tab and from any post, project, or "
"gist where it was attached. Show the user the exact media, get explicit "
"confirmation, then call again with confirm=true."
"gist where it was attached. Soft delete (restorable from admin trash). Show the user "
"the exact media, get explicit confirmation, then call again with confirm=true."
),
requires_auth=True,
params=(path("uid", "Attachment uid to delete."),),
params=(path("uid", "Attachment uid to delete."), confirm()),
),
Action(
name="view_leaderboard",
@@ -871,12 +888,13 @@ ACTIONS: tuple[Action, ...] = (
name="delete_gist",
method="POST",
path="/gists/delete/{gist_slug}",
summary="Delete a gist",
summary="Delete a gist (your own, or any gist when you are an administrator). Soft delete, confirmation required",
params=(
path(
"gist_slug",
"Exact gist slug copied from a /gists/... link in a listing response; do not build it from the title.",
),
confirm(),
),
),
Action(
@@ -932,8 +950,8 @@ ACTIONS: tuple[Action, ...] = (
name="delete_attachment",
method="DELETE",
path="/uploads/delete/{attachment_uid}",
summary="Delete an uploaded attachment",
params=(path("attachment_uid", "Uid of the attachment."),),
summary="Delete an uploaded attachment (your own, or anyone's when you are an administrator). Soft delete, confirmation required",
params=(path("attachment_uid", "Uid of the attachment."), confirm()),
),
Action(
name="admin_overview",
@@ -1080,8 +1098,8 @@ ACTIONS: tuple[Action, ...] = (
name="admin_delete_news",
method="POST",
path="/admin/news/{uid}/delete",
summary="Delete a news article",
params=(path("uid", "News uid."),),
summary="Delete a news article (admin only). Soft delete, confirmation required",
params=(path("uid", "News uid."), confirm()),
requires_admin=True,
),
Action(
@@ -1165,7 +1183,7 @@ ACTIONS: tuple[Action, ...] = (
path="/admin/users/{uid}/reset-ai-quota",
summary="Reset a user's AI quota (admin only)",
description="Clears the AI quota ledger for a specific user, allowing them to use AI features again.",
params=(path("uid", "User uid."),),
params=(path("uid", "User uid."), confirm()),
requires_admin=True,
),
Action(
@@ -1174,7 +1192,7 @@ ACTIONS: tuple[Action, ...] = (
path="/admin/media/{uid}/purge",
summary="Permanently remove soft-deleted media (admin only)",
description="Permanently deletes a soft-deleted attachment from the database and filesystem.",
params=(path("uid", "Attachment uid to purge."),),
params=(path("uid", "Attachment uid to purge."), confirm()),
requires_admin=True,
),
Action(
@@ -1183,6 +1201,7 @@ ACTIONS: tuple[Action, ...] = (
path="/admin/ai-quota/reset-guests",
summary="Reset all guest AI quotas (admin only)",
description="Clears the AI quota ledger for all guest sessions.",
params=(confirm(),),
requires_admin=True,
),
Action(
@@ -1191,6 +1210,7 @@ ACTIONS: tuple[Action, ...] = (
path="/admin/ai-quota/reset-all",
summary="Reset ALL AI quotas including member quotas (admin only)",
description="Clears the AI quota ledger for every user and guest. Use with caution.",
params=(confirm(),),
requires_admin=True,
),
Action(
@@ -81,6 +81,13 @@ CONTAINER_ACTIONS: tuple[Action, ...] = (
"start, stop, restart, pause, resume, delete, or sync.",
required=True,
),
arg(
"confirm",
"Required only for action=delete: set true ONLY after the user has explicitly "
"confirmed destroying the instance. Leave unset otherwise; the delete is refused "
"until you pass confirm=true.",
kind="boolean",
),
),
),
Action(
@@ -112,6 +119,13 @@ CONTAINER_ACTIONS: tuple[Action, ...] = (
"Command to run, e.g. 'git clone ... && ls'. Runs in /app already; do not prepend 'cd /app'.",
required=True,
),
arg(
"confirm",
"Required only when the command is destructive (rm, dd, truncate, drop, etc.): set "
"true ONLY after the user has explicitly confirmed. Leave unset otherwise; a "
"destructive command is refused until you pass confirm=true.",
kind="boolean",
),
),
),
Action(
@@ -50,6 +50,9 @@ CONFIRM_REQUIRED = {
"admin_reset_all_ai_quota",
"admin_reset_guest_ai_quota",
"admin_reset_user_ai_quota",
}
CONDITIONAL_CONFIRM = {
"container_instance_action",
"container_exec",
}
@@ -200,6 +203,12 @@ def confirmation_error(name: str, arguments: dict[str, Any]) -> ToolInputError |
f"such as rm, dd, truncate, or drop): {command!r}. Show the user the exact command, get "
"explicit confirmation, then call again with confirm=true."
)
if name in CONFIRM_REQUIRED:
return ToolInputError(
"This removes the item as a soft delete: it disappears from every surface and is only "
"restorable from the admin trash. Show the user exactly what will be deleted, get "
"explicit confirmation, then call again with confirm=true."
)
return None