Skip to content

Commit 4460784

Browse files
Copilotlpcox
andauthored
fix(guard): enforce explicit DIFC labels for notification management and repo creation writes
Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/52471578-c908-4c54-a0a3-4ed6d7167541 Co-authored-by: lpcox <[email protected]>
1 parent bbc6fbd commit 4460784

2 files changed

Lines changed: 76 additions & 18 deletions

File tree

guards/github-guard/rust-guard/src/labels/mod.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4614,22 +4614,31 @@ mod tests {
46144614
}
46154615

46164616
#[test]
4617-
fn test_apply_tool_labels_dismiss_notification_private_user() {
4617+
fn test_apply_tool_labels_notification_management_public_project_github() {
46184618
let ctx = default_ctx();
46194619
let tool_args = json!({ "threadId": "123" });
46204620

4621-
let (secrecy, integrity, _desc) = apply_tool_labels(
4621+
for tool in &[
46224622
"dismiss_notification",
4623-
&tool_args,
4624-
"",
4625-
vec![],
4626-
vec![],
4627-
String::new(),
4628-
&ctx,
4629-
);
4623+
"mark_all_notifications_read",
4624+
"manage_notification_subscription",
4625+
"manage_repository_notification_subscription",
4626+
] {
4627+
let (secrecy, integrity, _desc) =
4628+
apply_tool_labels(tool, &tool_args, "", vec![], vec![], String::new(), &ctx);
46304629

4631-
assert_eq!(secrecy, private_user_label(), "dismiss_notification must carry private:user secrecy");
4632-
assert_eq!(integrity, none_integrity("", &ctx), "dismiss_notification should have none-level integrity (references unknown content)");
4630+
assert!(
4631+
secrecy.is_empty(),
4632+
"{} should have empty (public) secrecy",
4633+
tool
4634+
);
4635+
assert_eq!(
4636+
integrity,
4637+
project_github_label(&ctx),
4638+
"{} should have project:github integrity",
4639+
tool
4640+
);
4641+
}
46334642
}
46344643

46354644
#[test]
@@ -4919,7 +4928,7 @@ mod tests {
49194928
"repo": "copilot"
49204929
});
49214930

4922-
let (_secrecy, integrity, _desc) = apply_tool_labels(
4931+
let (secrecy, integrity, _desc) = apply_tool_labels(
49234932
"fork_repository",
49244933
&tool_args,
49254934
repo_id,
@@ -4929,7 +4938,38 @@ mod tests {
49294938
&ctx,
49304939
);
49314940

4932-
assert_eq!(integrity, writer_integrity(repo_id, &ctx), "fork_repository should have writer integrity");
4941+
assert!(secrecy.is_empty(), "fork_repository should have empty (public) secrecy");
4942+
assert_eq!(
4943+
integrity,
4944+
writer_integrity("github", &ctx),
4945+
"fork_repository should have writer integrity in github scope"
4946+
);
4947+
}
4948+
4949+
#[test]
4950+
fn test_apply_tool_labels_create_repository_writer_integrity() {
4951+
let ctx = default_ctx();
4952+
let repo_id = "github/copilot";
4953+
let tool_args = json!({
4954+
"name": "new-repo"
4955+
});
4956+
4957+
let (secrecy, integrity, _desc) = apply_tool_labels(
4958+
"create_repository",
4959+
&tool_args,
4960+
repo_id,
4961+
vec![],
4962+
vec![],
4963+
String::new(),
4964+
&ctx,
4965+
);
4966+
4967+
assert!(secrecy.is_empty(), "create_repository should have empty (public) secrecy");
4968+
assert_eq!(
4969+
integrity,
4970+
writer_integrity("github", &ctx),
4971+
"create_repository should have writer integrity in github scope"
4972+
);
49334973
}
49344974

49354975
#[test]

guards/github-guard/rust-guard/src/labels/tool_rules.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -466,17 +466,26 @@ pub fn apply_tool_labels(
466466
}
467467

468468
// === Notifications (user-scoped, private) ===
469-
"list_notifications" | "get_notification_details"
470-
| "dismiss_notification" | "mark_all_notifications_read"
471-
| "manage_notification_subscription"
472-
| "manage_repository_notification_subscription" => {
469+
"list_notifications" | "get_notification_details" => {
473470
// Notifications are private to the authenticated user.
474471
// S = private:user
475472
// I = none (notifications reference external content of unknown trust)
476473
secrecy = private_user_label();
477474
integrity = vec![];
478475
}
479476

477+
// === Notification management (account-scoped writes) ===
478+
"dismiss_notification"
479+
| "mark_all_notifications_read"
480+
| "manage_notification_subscription"
481+
| "manage_repository_notification_subscription" => {
482+
// These operations change notification/subscription state and return minimal metadata.
483+
// S = public (empty); I = project:github
484+
secrecy = vec![];
485+
baseline_scope = "github".to_string();
486+
integrity = project_github_label(ctx);
487+
}
488+
480489
// === Private GitHub-controlled metadata (user-associated): PII/org-structure sensitive ===
481490
"get_me"
482491
| "get_teams"
@@ -562,14 +571,23 @@ pub fn apply_tool_labels(
562571

563572
// === Repo content and structure write operations ===
564573
"create_or_update_file" | "push_files" | "delete_file" | "create_branch"
565-
| "update_pull_request_branch" | "create_repository" | "fork_repository" => {
574+
| "update_pull_request_branch" => {
566575
// Write operations that modify repo content/structure.
567576
// S = S(repo) — response references repo-scoped content
568577
// I = writer (agent-authored content)
569578
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
570579
integrity = writer_integrity(repo_id, ctx);
571580
}
572581

582+
// === Repository creation/fork (user/org-scoped writes) ===
583+
"create_repository" | "fork_repository" => {
584+
// Creating/forking repositories is account-scoped and does not return repo content.
585+
// S = public (empty); I = writer(github)
586+
secrecy = vec![];
587+
baseline_scope = "github".to_string();
588+
integrity = writer_integrity("github", ctx);
589+
}
590+
573591
// === Projects write operations (org-scoped) ===
574592
"projects_write"
575593
// Deprecated aliases that map to projects_write

0 commit comments

Comments
 (0)