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
28 changes: 13 additions & 15 deletions guards/github-guard/rust-guard/src/labels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1742,11 +1742,10 @@ mod tests {
let ctx = default_ctx();
let tool_args = json!({});

// Pass repo_id = "github" so the baseline scope aligns with project_github_label's "github" scope
let (secrecy, integrity, _desc) = apply_tool_labels(
"get_me",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand All @@ -1769,7 +1768,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"get_teams",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand All @@ -1792,7 +1791,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"get_team_members",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand Down Expand Up @@ -1824,8 +1823,8 @@ mod tests {

// In test mode backend returns None → secrecy stays [] (public assumption)
assert_eq!(secrecy, vec![] as Vec<String>, "list_discussions secrecy inherits repo visibility");
// repo_private = None in tests → private_writer_integrity = [] → ensure_integrity_baseline returns none level
assert_eq!(integrity, none_integrity("github/copilot", &ctx), "list_discussions integrity is none-baseline when repo visibility unknown in tests");
// writer_integrity is used regardless of repo visibility — approved at resource level
assert_eq!(integrity, writer_integrity("github/copilot", &ctx), "list_discussions integrity is approved at resource level");
}

// -------------------------------------------------------------------------
Expand All @@ -1852,7 +1851,7 @@ mod tests {
);

assert_eq!(secrecy, vec![] as Vec<String>);
assert_eq!(integrity, none_integrity("github/copilot", &ctx));
assert_eq!(integrity, writer_integrity("github/copilot", &ctx));
}

// -------------------------------------------------------------------------
Expand All @@ -1879,7 +1878,7 @@ mod tests {
);

assert_eq!(secrecy, vec![] as Vec<String>);
assert_eq!(integrity, none_integrity("github/copilot", &ctx));
assert_eq!(integrity, writer_integrity("github/copilot", &ctx));
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -1914,11 +1913,10 @@ mod tests {
let ctx = default_ctx();
let tool_args = json!({ "username": "octocat" });

// repo_id = "user" aligns with the "user" scope used by reader_integrity("user", ctx)
let (secrecy, integrity, _desc) = apply_tool_labels(
"list_gists",
&tool_args,
"user",
"",
vec![],
vec![],
String::new(),
Expand All @@ -1941,7 +1939,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"get_gist",
&tool_args,
"user",
"",
vec![],
vec![],
String::new(),
Expand Down Expand Up @@ -2132,7 +2130,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"list_starred_repositories",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand All @@ -2155,7 +2153,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"search_orgs",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand All @@ -2178,7 +2176,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"list_global_security_advisories",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand All @@ -2201,7 +2199,7 @@ mod tests {
let (secrecy, integrity, _desc) = apply_tool_labels(
"get_global_security_advisory",
&tool_args,
"github",
"",
vec![],
vec![],
String::new(),
Expand Down
14 changes: 10 additions & 4 deletions guards/github-guard/rust-guard/src/labels/tool_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,17 +422,17 @@ pub fn apply_tool_labels(
"list_discussions" | "get_discussion" => {
// Discussions are user-submitted content, similar to issues.
// S = inherits from repo visibility
// I = private repos get approved; public repos deferred to response labeling
// I = approved — treat discussion content as approved at the resource level
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
integrity = private_writer_integrity(repo_id, repo_private, ctx);
integrity = writer_integrity(repo_id, ctx);
Comment on lines 422 to +427

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

list_discussions / get_discussion now assign writer_integrity(repo_id, ctx) unconditionally. This elevates user-generated discussion content to approved integrity even for public repos, which differs from the existing approach for other user-submitted repo content (e.g., issues use private_writer_integrity + per-item issue_integrity in response labeling). Consider keeping resource-level integrity at private_writer_integrity(repo_id, repo_private, ctx) (or reader_integrity for public) and adding response labeling for discussions to compute integrity per item (similar to issue_integrity), so min-integrity policies aren’t bypassed for untrusted public content.

This issue also appears on line 430 of the same file.

See below for a potential fix:

            // I = reader — resource-level integrity reflects read access; per-item
            //     labeling should be used to elevate trusted content if needed
            secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
            integrity = reader_integrity(repo_id, ctx);
        }

        "get_discussion_comments" => {
            // Discussion comments are user-submitted, lowest-trust user content.
            // S = inherits from repo visibility
            // I = reader — resource-level integrity reflects read access; per-item
            //     labeling should be used to elevate trusted content if needed
            secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
            integrity = reader_integrity(repo_id, ctx);

Copilot uses AI. Check for mistakes.
}

"get_discussion_comments" => {
// Discussion comments are user-submitted, lowest-trust user content.
// S = inherits from repo visibility
// I = private repos get approved; public repos deferred to response labeling
// I = approved — treat discussion comments as approved at the resource level
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
integrity = private_writer_integrity(repo_id, repo_private, ctx);
integrity = writer_integrity(repo_id, ctx);
}

"list_discussion_categories" => {
Expand All @@ -450,6 +450,7 @@ pub fn apply_tool_labels(
// S = private:user (conservative — some gists may be secret)
// I = reader (user content, no repo-level trust signal)
secrecy = private_user_label();
baseline_scope = "user".to_string();
integrity = reader_integrity("user", ctx);
}

Expand All @@ -469,6 +470,7 @@ pub fn apply_tool_labels(
// S = private:user
// I = project:github (GitHub-controlled metadata)
secrecy = private_user_label();
baseline_scope = "github".to_string();
integrity = project_github_label(ctx);
}

Expand All @@ -477,6 +479,7 @@ pub fn apply_tool_labels(
// S = private:user (org membership is sensitive)
// I = project:github (GitHub-controlled metadata)
secrecy = private_user_label();
baseline_scope = "github".to_string();
integrity = project_github_label(ctx);
}

Expand Down Expand Up @@ -504,6 +507,7 @@ pub fn apply_tool_labels(
// S = private:user (personal data)
// I = project:github (GitHub-controlled metadata)
secrecy = private_user_label();
baseline_scope = "github".to_string();
integrity = project_github_label(ctx);
}

Expand All @@ -513,6 +517,7 @@ pub fn apply_tool_labels(
// S = public (empty)
// I = project:github (GitHub-controlled metadata)
secrecy = vec![];
baseline_scope = "github".to_string();
integrity = project_github_label(ctx);
}

Expand All @@ -522,6 +527,7 @@ pub fn apply_tool_labels(
// S = public (empty) — these are published advisories
// I = project:github — curated by GitHub security team
secrecy = vec![];
baseline_scope = "github".to_string();
integrity = project_github_label(ctx);
}

Expand Down
Loading