Skip to content

approval-labels / refusal-labels ignored on the issue_read tool path #9382

Description

@polmichel

What happens

With a policy of min-integrity: approved + approval-labels: ["<label>"], applying that label to an issue should promote it to approved so the agent can read it. This works when the issue is read over the REST proxy, but not when read via the issue_read MCP tool: the label is ignored, the issue stays none/unapproved, and the read is DIFC-filtered. Confirmed on v0.3.30 and v0.4.1, with github-mcp-server v1.4.0.

Root cause

guards/github-guard/rust-guard/src/labels/helpers.rs, extract_github_label_names (line 394) reads label.name from each element, assuming labels are REST objects ([{"name":"x"}]). But github-mcp-server's issue_read returns labels as bare strings (["x"]), so .get("name") is None, the function returns an empty list, no label matches, and promotion is skipped. The guard already handles one REST-vs-MCP shape difference (it enriches author_association when the MCP path omits it, helpers.rs:1928); the same problem for labels was never handled.

Reproduce

Take an issue authored by a non-collaborator (author_association: NONE) carrying the label approved-label. Run github-mcp-server v1.4.0 as backend, put the guard (awmg, unified mode, --guards-mode filter) in front with policy allow-only: { min-integrity: approved, approval-labels: ["approved-label"], repos: all }, then read the issue via the issue_read tool. The read is filtered even though the label is present: [DIFC-FILTERED] issue_read integrity_tags:["none:all","unapproved:all"] author_association:"NONE". Controls: removing the label still filters (correct baseline); adding the author to trusted-users returns it (author promotion works); reading the same issue through the REST proxy returns it (label promotion works on the REST path).

Unit test reproduction

  #[test]
  fn test_has_approval_label_matches_bare_string_label() {
      let ctx = PolicyContext {
          approval_labels: vec!["approved".to_string()],
          ..Default::default()
      };
      let item = serde_json::json!({"labels": ["approved"]});   // MCP server shape
      assert!(has_approval_label(&item, &ctx));
  }

Impact

Approval-label review gates are the documented way for us to admit below-approved (e.g. community-reported) content to an agent, so any workflow relying on approval-labels + the github toolset cannot read the labeled item. Since extract_github_label_names is shared, refusal-labels is broken the same way: a refusal label on a tool-read item is ignored and not demoted to none (the security-relevant direction). Present in v0.3.30 through v0.4.1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions