@@ -341,6 +341,14 @@ pub fn apply_tool_labels(
341341 integrity = writer_integrity ( repo_id, ctx) ;
342342 }
343343
344+ // === Code quality findings (repo-scoped) ===
345+ // S = S(repo) — inherits from repository visibility
346+ // I = writer (requires repo write access to post/view code quality findings)
347+ "get_code_quality_finding" => {
348+ secrecy = apply_repo_visibility_secrecy ( & owner, & repo, repo_id, secrecy, ctx) ;
349+ integrity = writer_integrity ( repo_id, ctx) ;
350+ }
351+
344352 // === Actions: Workflow/Artifact Metadata and Artifact Downloads ===
345353 "actions_get" => {
346354 let method = tool_args. get ( "method" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
@@ -355,6 +363,33 @@ pub fn apply_tool_labels(
355363 integrity = writer_integrity ( repo_id, ctx) ;
356364 }
357365
366+ // === UI metadata dispatch (repo/org-scoped, method-dependent) ===
367+ // Mirrors existing rules for list_label, list_branches, list_issue_types,
368+ // list_issue_fields, and list_repository_collaborators.
369+ "ui_get" => {
370+ let method = tool_args. get ( "method" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
371+ match method {
372+ // Repo-scoped metadata: labels, milestones, branches
373+ // S = S(repo); I = writer
374+ "labels" | "milestones" | "branches" => {
375+ secrecy = apply_repo_visibility_secrecy ( & owner, & repo, repo_id, secrecy, ctx) ;
376+ integrity = writer_integrity ( repo_id, ctx) ;
377+ }
378+ // Org-level type/field definitions (GitHub-controlled metadata)
379+ // S = inherits from org; I = approved:github
380+ "issue_types" | "issue_fields" => {
381+ integrity = project_github_label ( ctx) ;
382+ }
383+ // Access-sensitive membership/reviewer data
384+ // S = private policy scope; I = reader
385+ "assignees" | "reviewers" => {
386+ secrecy = policy_private_scope_label ( & owner, & repo, repo_id, ctx) ;
387+ integrity = reader_integrity ( repo_id, ctx) ;
388+ }
389+ _ => { }
390+ }
391+ }
392+
358393 // === Repo-scoped resources: visibility-inherited secrecy, approved integrity ===
359394 // S = inherits from repo visibility; I = approved (writer-level)
360395 "actions_list"
@@ -649,6 +684,19 @@ pub fn apply_tool_labels(
649684 integrity = writer_integrity ( repo_id, ctx) ;
650685 }
651686
687+ // === User SSH/GPG key management (account-scoped writes) ===
688+ // Pre-emptive synthetic guard entries for CLI-only operations:
689+ // `gh ssh-key add` → POST /user/keys and /user/ssh_signing_keys
690+ // `gh gpg-key add` → POST /user/gpg_keys
691+ // Adding auth/signing keys is a high-risk account-level write operation.
692+ // S = private:user (user-account-scoped sensitive data)
693+ // I = writer(user) (requires authenticated account write access)
694+ "add_gpg_key" | "add_ssh_key" => {
695+ secrecy = private_user_label ( ) ;
696+ baseline_scope = Cow :: Borrowed ( scope_names:: USER ) ;
697+ integrity = writer_integrity ( scope_names:: USER , ctx) ;
698+ }
699+
652700 // === Dynamic toolset enablement (capability expansion) ===
653701 "enable_toolset" => {
654702 // Enabling a toolset expands the agent's runtime capability set.
@@ -1297,4 +1345,149 @@ mod tests {
12971345 "delete_gist: destructive operation must require writer-level user integrity" ,
12981346 ) ;
12991347 }
1348+
1349+ #[ test]
1350+ fn apply_tool_labels_get_code_quality_finding_inherits_repo_visibility ( ) {
1351+ let ctx = default_ctx ( ) ;
1352+ let args = serde_json:: json!( { "owner" : "octocat" , "repo" : "hello-world" } ) ;
1353+ let repo_id = "octocat/hello-world" ;
1354+
1355+ let ( _, integrity, _) = super :: apply_tool_labels (
1356+ "get_code_quality_finding" ,
1357+ & args,
1358+ repo_id,
1359+ vec ! [ ] ,
1360+ vec ! [ ] ,
1361+ String :: new ( ) ,
1362+ & ctx,
1363+ ) ;
1364+ assert_eq ! (
1365+ integrity,
1366+ writer_integrity( repo_id, & ctx) ,
1367+ "get_code_quality_finding: expected writer-level integrity" ,
1368+ ) ;
1369+ }
1370+
1371+ #[ test]
1372+ fn apply_tool_labels_ui_get_labels_milestones_branches_are_repo_scoped ( ) {
1373+ let ctx = default_ctx ( ) ;
1374+ let repo_id = "octocat/hello-world" ;
1375+ let expected_integrity = writer_integrity ( repo_id, & ctx) ;
1376+
1377+ for method in & [ "labels" , "milestones" , "branches" ] {
1378+ let args = serde_json:: json!( {
1379+ "owner" : "octocat" ,
1380+ "repo" : "hello-world" ,
1381+ "method" : method,
1382+ } ) ;
1383+ let ( _, integrity, _) = super :: apply_tool_labels (
1384+ "ui_get" ,
1385+ & args,
1386+ repo_id,
1387+ vec ! [ ] ,
1388+ vec ! [ ] ,
1389+ String :: new ( ) ,
1390+ & ctx,
1391+ ) ;
1392+ assert_eq ! (
1393+ integrity, expected_integrity,
1394+ "ui_get method={method}: expected writer-level integrity" ,
1395+ ) ;
1396+ }
1397+ }
1398+
1399+ #[ test]
1400+ fn apply_tool_labels_ui_get_issue_types_and_fields_are_github_approved ( ) {
1401+ let ctx = default_ctx ( ) ;
1402+ let repo_id = "octocat/hello-world" ;
1403+
1404+ for ( method, standalone) in & [ ( "issue_types" , "list_issue_types" ) , ( "issue_fields" , "list_issue_fields" ) ] {
1405+ let args = serde_json:: json!( {
1406+ "owner" : "octocat" ,
1407+ "repo" : "hello-world" ,
1408+ "method" : method,
1409+ } ) ;
1410+ let ( _, integrity, _) = super :: apply_tool_labels (
1411+ "ui_get" ,
1412+ & args,
1413+ repo_id,
1414+ vec ! [ ] ,
1415+ vec ! [ ] ,
1416+ String :: new ( ) ,
1417+ & ctx,
1418+ ) ;
1419+ // Mirror the corresponding standalone tool: ui_get issue_types/issue_fields
1420+ // should produce the same integrity as list_issue_types/list_issue_fields.
1421+ let ( _, expected_integrity, _) = super :: apply_tool_labels (
1422+ standalone,
1423+ & args,
1424+ repo_id,
1425+ vec ! [ ] ,
1426+ vec ! [ ] ,
1427+ String :: new ( ) ,
1428+ & ctx,
1429+ ) ;
1430+ assert_eq ! (
1431+ integrity, expected_integrity,
1432+ "ui_get method={method}: expected same integrity as {standalone}" ,
1433+ ) ;
1434+ }
1435+ }
1436+
1437+ #[ test]
1438+ fn apply_tool_labels_ui_get_assignees_and_reviewers_are_access_sensitive ( ) {
1439+ let ctx = default_ctx ( ) ;
1440+ let repo_id = "octocat/hello-world" ;
1441+ let expected_integrity = reader_integrity ( repo_id, & ctx) ;
1442+
1443+ for method in & [ "assignees" , "reviewers" ] {
1444+ let args = serde_json:: json!( {
1445+ "owner" : "octocat" ,
1446+ "repo" : "hello-world" ,
1447+ "method" : method,
1448+ } ) ;
1449+ let ( secrecy, integrity, _) = super :: apply_tool_labels (
1450+ "ui_get" ,
1451+ & args,
1452+ repo_id,
1453+ vec ! [ ] ,
1454+ vec ! [ ] ,
1455+ String :: new ( ) ,
1456+ & ctx,
1457+ ) ;
1458+ let _ = secrecy; // secrecy is policy_private_scope_label (backend unavailable in tests)
1459+ assert_eq ! (
1460+ integrity, expected_integrity,
1461+ "ui_get method={method}: expected reader-level integrity" ,
1462+ ) ;
1463+ }
1464+ }
1465+
1466+ #[ test]
1467+ fn apply_tool_labels_add_gpg_key_and_add_ssh_key_are_user_private_writes ( ) {
1468+ let ctx = default_ctx ( ) ;
1469+ let args = serde_json:: json!( { } ) ;
1470+ let expected_secrecy = private_user_label ( ) ;
1471+ let expected_integrity = writer_integrity ( scope_names:: USER , & ctx) ;
1472+
1473+ for tool in & [ "add_gpg_key" , "add_ssh_key" ] {
1474+ let ( secrecy, integrity, _) = super :: apply_tool_labels (
1475+ tool,
1476+ & args,
1477+ "" ,
1478+ vec ! [ ] ,
1479+ vec ! [ ] ,
1480+ String :: new ( ) ,
1481+ & ctx,
1482+ ) ;
1483+ assert_eq ! (
1484+ secrecy, expected_secrecy,
1485+ "{tool}: must be user-private (secrecy = private:user)" ,
1486+ ) ;
1487+ assert_eq ! (
1488+ integrity, expected_integrity,
1489+ "{tool}: must require writer-level user integrity" ,
1490+ ) ;
1491+ }
1492+ }
13001493}
0 commit comments