diff --git a/guards/github-guard/rust-guard/src/labels/tool_rules.rs b/guards/github-guard/rust-guard/src/labels/tool_rules.rs index aed07eb9..f7455cba 100644 --- a/guards/github-guard/rust-guard/src/labels/tool_rules.rs +++ b/guards/github-guard/rust-guard/src/labels/tool_rules.rs @@ -171,6 +171,16 @@ pub fn apply_tool_labels( secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx); } + // === Modifying Repository Operations (blocked: unsupported gh repo operations) === + "archive_repository" | "unarchive_repository" | "rename_repository" => { + // All modifying `gh repo` operations (archive, unarchive, rename) are treated as + // unsupported for automated agents — the same policy as transfer_repository. + // Blocking is enforced in label_resource via is_blocked_tool(); this arm applies + // repo-visibility secrecy so the resource is correctly classified before the + // integrity override happens in label_resource. + secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx); + } + // Search issues: extract repo scope from query or tool_args when available "search_issues" => { let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo); diff --git a/guards/github-guard/rust-guard/src/tools.rs b/guards/github-guard/rust-guard/src/tools.rs index 89f4ae3f..35e3bc6d 100644 --- a/guards/github-guard/rust-guard/src/tools.rs +++ b/guards/github-guard/rust-guard/src/tools.rs @@ -38,11 +38,13 @@ pub const WRITE_OPERATIONS: &[&str] = &[ // Dynamically enables additional toolsets, expanding the agent's capability set "enable_toolset", // Pre-emptive entries for anticipated future MCP tools (no equivalent tool today) - "archive_repository", // gh repo archive - "transfer_issue", // gh issue transfer - "transfer_repository", // gh repo transfer — blocked: repo ownership transfer is irreversible - "pin_issue", // gh issue pin - "unpin_issue", // gh issue unpin + "archive_repository", // gh repo archive — blocked: repo settings change unsupported + "unarchive_repository", // gh repo unarchive — blocked: symmetric to archive_repository + "rename_repository", // gh repo rename — blocked: breaks clone URLs and integrations + "transfer_issue", // gh issue transfer + "transfer_repository", // gh repo transfer — blocked: repo ownership transfer is irreversible + "pin_issue", // gh issue pin + "unpin_issue", // gh issue unpin "enable_workflow", // gh workflow enable "disable_workflow", // gh workflow disable "set_secret", // gh secret set @@ -116,8 +118,17 @@ pub fn is_unlock_operation(tool_name: &str) -> bool { /// Current entries: /// - `transfer_repository`: repository ownership transfer is irreversible and /// must never be performed by an automated agent. +/// - `archive_repository`: archives a repository, restricting contributions; unsupported as an +/// agent operation. +/// - `unarchive_repository`: re-enables contributions to a previously archived repository; +/// symmetric to `archive_repository` and equally unsupported. +/// - `rename_repository`: renames a repository, breaking all clone URLs, webhooks, and external +/// references; unsupported as an agent operation. pub fn is_blocked_tool(tool_name: &str) -> bool { - matches!(tool_name, "transfer_repository") + matches!( + tool_name, + "transfer_repository" | "archive_repository" | "unarchive_repository" | "rename_repository" + ) } #[cfg(test)] @@ -132,6 +143,17 @@ mod tests { ); } + #[test] + fn test_is_blocked_tool_repo_modifying_operations() { + for op in &["archive_repository", "unarchive_repository", "rename_repository"] { + assert!( + is_blocked_tool(op), + "{} must be unconditionally blocked (modifying gh repo operation)", + op + ); + } + } + #[test] fn test_is_blocked_tool_other_write_ops_not_blocked() { // Regular write operations should not be blocked @@ -152,6 +174,17 @@ mod tests { ); } + #[test] + fn test_repo_modifying_operations_are_write_operations() { + for op in &["archive_repository", "unarchive_repository", "rename_repository"] { + assert!( + is_write_operation(op), + "{} must be classified as a write operation", + op + ); + } + } + #[test] fn test_pin_unpin_issue_are_write_operations() { assert!(