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
80 changes: 80 additions & 0 deletions guards/github-guard/rust-guard/src/labels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4712,4 +4712,84 @@ mod tests {

assert_eq!(integrity, writer_integrity(repo_id, &ctx), "fork_repository should have writer integrity");
}

#[test]
fn test_apply_tool_labels_edit_repository_writer_integrity() {
let ctx = default_ctx();
let repo_id = "github/copilot";
let tool_args = json!({
"owner": "github",
"repo": "copilot",
});

let (_secrecy, integrity, _desc) = apply_tool_labels(
"edit_repository",
&tool_args,
repo_id,
vec![],
vec![],
String::new(),
&ctx,
);

assert_eq!(integrity, writer_integrity(repo_id, &ctx), "edit_repository should have writer integrity");
}

#[test]
fn test_apply_tool_labels_revert_pull_request_writer_integrity() {
let ctx = default_ctx();
let repo_id = "github/copilot";
let tool_args = json!({
"owner": "github",
"repo": "copilot",
"pullNumber": 42,
});

let (_secrecy, integrity, _desc) = apply_tool_labels(
"revert_pull_request",
&tool_args,
repo_id,
vec![],
vec![],
String::new(),
&ctx,
);

assert_eq!(integrity, writer_integrity(repo_id, &ctx), "revert_pull_request should have writer integrity");
}

#[test]
fn test_apply_tool_labels_deploy_key_operations_private_secrecy() {
let ctx = default_ctx();
let repo_id = "github/copilot";
let tool_args = json!({
"owner": "github",
"repo": "copilot",
});

for tool_name in &["add_deploy_key", "delete_deploy_key"] {
let (secrecy, integrity, _desc) = apply_tool_labels(
tool_name,
&tool_args,
repo_id,
vec![],
vec![],
String::new(),
&ctx,
);

assert_eq!(
secrecy,
super::helpers::policy_private_scope_label("github", "copilot", repo_id, &ctx),
"{} should have private-scoped secrecy",
tool_name
);
assert_eq!(
integrity,
writer_integrity(repo_id, &ctx),
"{} should have writer integrity",
tool_name
);
}
}
}
25 changes: 25 additions & 0 deletions guards/github-guard/rust-guard/src/labels/tool_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,31 @@ pub fn apply_tool_labels(
integrity = writer_integrity(repo_id, ctx);
}

// === Repository settings edit (can change visibility) ===
"edit_repository" => {
// Can change repo visibility, security settings, default branch.
// S = S(repo); I = writer (requires admin access)
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
integrity = writer_integrity(repo_id, ctx);
}

// === PR revert (creates revert branch + PR) ===
"revert_pull_request" => {
// Creates a new branch + PR reverting a merged PR.
// S = S(repo); I = writer
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
integrity = writer_integrity(repo_id, ctx);
}

// === Deploy key management (SSH key with optional write access) ===
"add_deploy_key" | "delete_deploy_key" => {
// Manages SSH deploy keys — `add_deploy_key` may grant persistent write access.
// S = at least private; scope is policy-dependent (may be unscoped, owner-scoped, or repo-scoped)
// I = writer (requires admin access)
secrecy = policy_private_scope_label(&owner, &repo, repo_id, ctx);
integrity = writer_integrity(repo_id, ctx);
}

// === Star/unstar operations (public metadata) ===
"star_repository" | "unstar_repository" => {
// Starring is a public action; response is minimal metadata.
Expand Down
23 changes: 23 additions & 0 deletions guards/github-guard/rust-guard/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ pub const WRITE_OPERATIONS: &[&str] = &[
"rerun_workflow_run", // gh run rerun — reruns a completed workflow run
"rerun_failed_jobs", // gh run rerun --failed — reruns only failed jobs
"rerun_workflow_job", // gh run rerun --job — reruns a specific job
// Pre-emptive: gh repo edit (PATCH /repos/{owner}/{repo}) — can change visibility, security settings
"edit_repository",
// Pre-emptive: gh pr revert (GraphQL revertPullRequest) — creates revert branch + PR
"revert_pull_request",
// Pre-emptive: gh repo deploy-key add/delete — SSH key with optional write access
"add_deploy_key",
"delete_deploy_key",
];

/// Read-write operations that both read and modify data
Expand Down Expand Up @@ -210,6 +217,22 @@ mod tests {
}
}

#[test]
fn test_cli_gap_operations_are_write_operations() {
for op in &[
"edit_repository",
"revert_pull_request",
"add_deploy_key",
"delete_deploy_key",
] {
assert!(
is_write_operation(op),
"{} must be classified as a write operation",
op
);
}
}

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