Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions guards/github-guard/rust-guard/src/labels/tool_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:<owner>
if !owner.is_empty() {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Comment on lines +677 to +685
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New DIFC rule for enable_toolset isn’t covered by existing apply_tool_labels tests. Since this tool controls runtime capability expansion, add a unit test (in labels/mod.rs test suite) asserting it returns writer-level integrity on the intended scope (and public secrecy) to prevent regressions.

Copilot uses AI. Check for mistakes.

// === Star/unstar operations (public metadata) ===
"star_repository" | "unstar_repository" => {
// Starring is a public action; response is minimal metadata.
Expand All @@ -679,6 +693,33 @@ 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.
// 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);
}

_ => {
// Default: inherit provided labels
}
Expand Down
58 changes: 58 additions & 0 deletions guards/github-guard/rust-guard/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
);
}
}
}
Loading