From 5110c2a062262e86dadc07a3cf4067e08796ad82 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 14 Apr 2026 08:02:33 -0700 Subject: [PATCH 1/2] fix(guard): cover deprecated tool aliases, enable_toolset DIFC rule, and pre-emptive CLI entries - Add 5 deprecated MCP tool aliases to write classification: run_workflow, delete_workflow_run_logs, add_project_item, delete_project_item (WRITE_OPERATIONS), update_project_item (READ_WRITE_OPERATIONS) - Add DIFC labeling rules for deprecated aliases by extending existing match arms (projects_write, actions_run_trigger) - Add explicit enable_toolset DIFC rule with writer-level integrity to prevent low-trust agents from self-escalating - Add 6 pre-emptive CLI entries: update_issue_comment, delete_issue_comment, create_release, edit_release, delete_release, delete_gist - Add DIFC labeling rules for all pre-emptive CLI entries - Add tests for new classifications Fixes #3720 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../rust-guard/src/labels/tool_rules.rs | 42 +++++++++++++- guards/github-guard/rust-guard/src/tools.rs | 58 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) 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 602110c2..cb1ae90f 100644 --- a/guards/github-guard/rust-guard/src/labels/tool_rules.rs +++ b/guards/github-guard/rust-guard/src/labels/tool_rules.rs @@ -591,7 +591,9 @@ pub fn apply_tool_labels( } // === Projects write operations (org-scoped) === - "projects_write" => { + "projects_write" + // Deprecated aliases that map to projects_write + | "add_project_item" | "update_project_item" | "delete_project_item" => { // Projects are org-scoped; write responses carry the same labels as reads. // I = approved: if !owner.is_empty() { @@ -609,7 +611,9 @@ pub fn apply_tool_labels( } // === Actions: Workflow run triggers === - "actions_run_trigger" => { + "actions_run_trigger" + // Deprecated aliases that map to actions_run_trigger + | "run_workflow" | "delete_workflow_run_logs" => { // Triggering a workflow run returns repo-scoped metadata. // S = S(repo); I = writer secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx); @@ -670,6 +674,16 @@ pub fn apply_tool_labels( integrity = writer_integrity(repo_id, ctx); } + // === Dynamic toolset enablement (capability expansion) === + "enable_toolset" => { + // Enabling a toolset expands the agent's runtime capability set. + // Requires writer-level integrity to prevent low-trust agents from + // self-escalating by enabling additional tool groups. + // S = public (empty — no repository-scoped data); I = writer (global) + baseline_scope = "github".to_string(); + integrity = writer_integrity("github", ctx); + } + // === Star/unstar operations (public metadata) === "star_repository" | "unstar_repository" => { // Starring is a public action; response is minimal metadata. @@ -679,6 +693,30 @@ pub fn apply_tool_labels( integrity = project_github_label(ctx); } + // === Issue/PR comment editing/deletion (pre-emptive) === + "update_issue_comment" | "delete_issue_comment" => { + // Editing or deleting an issue/PR comment is a repo-scoped write. + // S = S(repo); I = writer + secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx); + integrity = writer_integrity(repo_id, ctx); + } + + // === Release management (pre-emptive) === + "create_release" | "edit_release" | "delete_release" => { + // Release operations are repo-scoped writes. + // S = S(repo); I = writer + secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx); + integrity = writer_integrity(repo_id, ctx); + } + + // === Gist deletion (pre-emptive) === + "delete_gist" => { + // Gist deletion is a write on user-scoped content. + // S = public (gists can be public/secret); I = writer (global) + baseline_scope = "github".to_string(); + integrity = writer_integrity("github", ctx); + } + _ => { // Default: inherit provided labels } diff --git a/guards/github-guard/rust-guard/src/tools.rs b/guards/github-guard/rust-guard/src/tools.rs index 91e18a87..8dcd7859 100644 --- a/guards/github-guard/rust-guard/src/tools.rs +++ b/guards/github-guard/rust-guard/src/tools.rs @@ -59,6 +59,20 @@ pub const WRITE_OPERATIONS: &[&str] = &[ // Pre-emptive: gh repo deploy-key add/delete — SSH key with optional write access "add_deploy_key", "delete_deploy_key", + // Deprecated alias coverage (guard sees alias name before backend resolves it) + "run_workflow", // deprecated alias for actions_run_trigger (POST workflow dispatch) + "delete_workflow_run_logs", // deprecated alias for actions_run_trigger (DELETE run logs) + "add_project_item", // deprecated alias for projects_write (addProjectV2ItemById) + "delete_project_item", // deprecated alias for projects_write (deleteProjectV2Item) + // Pre-emptive: issue/PR comment editing/deletion (gh issue/pr comment --edit/--delete) + "update_issue_comment", // PATCH /repos/.../issues/comments/{id} + "delete_issue_comment", // DELETE /repos/.../issues/comments/{id} + // Pre-emptive: release management (gh release create/edit/delete) + "create_release", // POST /repos/.../releases + "edit_release", // PATCH /repos/.../releases/{id} + "delete_release", // DELETE /repos/.../releases/{id} + // Pre-emptive: gist deletion (gh gist delete) + "delete_gist", // DELETE /gists/{gist_id} ]; /// Read-write operations that both read and modify data @@ -73,6 +87,8 @@ pub const READ_WRITE_OPERATIONS: &[&str] = &[ // Pre-emptive entries for anticipated future MCP tools (no equivalent tool today) // gh agent-task create — creates a Copilot coding-agent job (branch + PR); blocked as unsupported "create_agent_task", + // Deprecated alias coverage + "update_project_item", // deprecated alias for projects_write (updateProjectV2ItemFieldValue) ]; /// Check if a tool is a write operation @@ -249,4 +265,46 @@ mod tests { "create_agent_task should not be in WRITE_OPERATIONS (it is in READ_WRITE_OPERATIONS)" ); } + + #[test] + fn test_deprecated_alias_write_operations() { + for op in &[ + "run_workflow", + "delete_workflow_run_logs", + "add_project_item", + "delete_project_item", + ] { + assert!( + is_write_operation(op), + "{} (deprecated alias) must be classified as a write operation", + op + ); + } + } + + #[test] + fn test_deprecated_alias_read_write_operations() { + assert!( + is_read_write_operation("update_project_item"), + "update_project_item (deprecated alias) must be classified as a read-write operation" + ); + } + + #[test] + fn test_preemptive_cli_write_operations() { + for op in &[ + "update_issue_comment", + "delete_issue_comment", + "create_release", + "edit_release", + "delete_release", + "delete_gist", + ] { + assert!( + is_write_operation(op), + "{} (pre-emptive CLI) must be classified as a write operation", + op + ); + } + } } From 0b4c9bad23a0d90efe266b876a5b92b778ed6d8f Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 14 Apr 2026 08:42:41 -0700 Subject: [PATCH 2/2] Update guards/github-guard/rust-guard/src/labels/tool_rules.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- guards/github-guard/rust-guard/src/labels/tool_rules.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 cb1ae90f..9daa3384 100644 --- a/guards/github-guard/rust-guard/src/labels/tool_rules.rs +++ b/guards/github-guard/rust-guard/src/labels/tool_rules.rs @@ -712,9 +712,12 @@ pub fn apply_tool_labels( // === Gist deletion (pre-emptive) === "delete_gist" => { // Gist deletion is a write on user-scoped content. - // S = public (gists can be public/secret); I = writer (global) - baseline_scope = "github".to_string(); - integrity = writer_integrity("github", ctx); + // Conservatively treat gists as private/user-scoped, consistent with + // other gist operations that may target secret gists. + // S = private_user; I = writer(user) + secrecy = private_user_label(); + baseline_scope = "user".to_string(); + integrity = writer_integrity("user", ctx); } _ => {