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
16 changes: 14 additions & 2 deletions guards/github-guard/rust-guard/src/labels/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub struct IssueAuthorInfo {
#[derive(Debug, Clone)]
pub struct CollaboratorPermission {
pub permission: Option<String>,
#[cfg_attr(not(test), allow(dead_code))]
#[cfg(test)]
pub login: Option<String>,
}

Expand Down Expand Up @@ -494,6 +494,7 @@ pub fn get_collaborator_permission_with_callback(
));
return cached.map(|permission| CollaboratorPermission {
permission: Some(permission),
#[cfg(test)]
login: Some(username.to_string()),
});
}
Expand Down Expand Up @@ -559,20 +560,31 @@ pub fn get_collaborator_permission_with_callback(
.and_then(|v| v.as_str())
.map(String::from);

#[cfg(test)]
let login = data
.get("user")
.and_then(|u| u.get("login"))
.and_then(|v| v.as_str())
.map(String::from);

#[cfg(not(test))]
crate::log_info(&format!(
"get_collaborator_permission: {}/{} user {} → permission={:?}",
owner, repo, username, permission
));
#[cfg(test)]
crate::log_info(&format!(
"get_collaborator_permission: {}/{} user {} → permission={:?} login={:?}",
owner, repo, username, permission, login
));

set_cached_collaborator_permission(&cache_key, permission.clone());

Some(CollaboratorPermission { permission, login })
Some(CollaboratorPermission {
permission,
#[cfg(test)]
login,
})
Comment on lines +583 to +587
}

pub fn get_collaborator_permission(
Expand Down
42 changes: 18 additions & 24 deletions guards/github-guard/rust-guard/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,36 +137,19 @@ pub fn is_unlock_operation(tool_name: &str) -> bool {

/// Tools that are unconditionally blocked regardless of agent integrity.
///
/// These operations are too dangerous or unsupported to ever permit via an agent.
/// Entries here should also appear in `WRITE_OPERATIONS` or `READ_WRITE_OPERATIONS`
/// so the tool is still subject to all normal write-path checks before being denied.
/// Keep sorted for `binary_search` correctness (see `blocked_tools_are_sorted` test).
/// Entries here should also appear in `WRITE_OPERATIONS` or `READ_WRITE_OPERATIONS`.
pub const BLOCKED_TOOLS: &[&str] = &[
"transfer_repository", // irreversible ownership transfer
"archive_repository", // repo settings change; unsupported
"unarchive_repository", // symmetric to archive_repository
"rename_repository", // breaks clone URLs and integrations
"create_agent_task", // unsupported agent-task creation
"rename_repository", // breaks clone URLs and integrations
"transfer_repository", // irreversible ownership transfer
"unarchive_repository", // symmetric to archive_repository
];

/// Check if a tool is unconditionally blocked (always denied regardless of agent integrity).
///
/// Blocked tools are listed here when the operation is considered too dangerous
/// to ever permit via an agent, even if the agent would otherwise satisfy the
/// integrity requirements for a normal write operation.
///
/// 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.
/// - `create_agent_task`: creates a Copilot coding-agent job that opens a branch and PR;
/// unsupported as a directly invocable agent operation.
/// Returns `true` if `tool_name` is in [`BLOCKED_TOOLS`] — denied regardless of agent integrity.
pub fn is_blocked_tool(tool_name: &str) -> bool {
BLOCKED_TOOLS.contains(&tool_name)
BLOCKED_TOOLS.binary_search(&tool_name).is_ok()
}

#[cfg(test)]
Expand Down Expand Up @@ -205,6 +188,17 @@ mod tests {
);
}

#[test]
fn blocked_tools_are_sorted() {
let mut sorted = BLOCKED_TOOLS.to_vec();
sorted.sort_unstable();
assert_eq!(
BLOCKED_TOOLS,
sorted.as_slice(),
"BLOCKED_TOOLS must be kept in sorted order for binary_search correctness"
);
}

#[test]
fn test_is_blocked_tool_transfer_repository() {
assert!(
Expand Down
Loading