Skip to content

feat: implement blocked-users and approval-labels in GitHub guard#2241

Merged
lpcox merged 2 commits intomainfrom
copilot/implement-blocked-users-and-approval-labels
Mar 21, 2026
Merged

feat: implement blocked-users and approval-labels in GitHub guard#2241
lpcox merged 2 commits intomainfrom
copilot/implement-blocked-users-and-approval-labels

Conversation

Copy link
Contributor

Copilot AI commented Mar 20, 2026

Summary

Implements the blocked-users and approval-labels additions from the MCP gateway spec (github/gh-aw#22023).

What changed

New integrity level: blocked
A new integrity level blocked sits below none in the hierarchy:

blocked < none < unapproved < approved < merged

Items authored by users in the blocked-users list receive blocked integrity tags (e.g. blocked:owner/repo). Since no agent is ever assigned a blocked: tag, these items are unconditionally denied by the DIFC filter.

blocked-users field
An optional array of GitHub usernames whose content items are always blocked regardless of labels or min-integrity. approval-labels cannot override a blocked-user exclusion.

approval-labels field
An optional array of GitHub label names that promote an item's effective integrity to at least approved when present on the item. Uses max(base_integrity, approved) so it never lowers an item already at merged.

Effective integrity computation (per spec §4.6.2)

1. Start with base integrity (from GitHub metadata)
2. IF author is in blocked-users → effective = blocked (always denied)
3. ELSE IF item has any approval label → effective = max(base, approved)
4. ELSE → effective = base

Files changed

File Change
guards/github-guard/rust-guard/src/labels/constants.rs Add BLOCKED_PREFIX/BLOCKED_BASE
guards/github-guard/rust-guard/src/labels/helpers.rs Add fields to PolicyContext; add blocked_integrity, is_blocked_user, has_approval_label helpers; update pr_integrity, issue_integrity, commit_integrity
guards/github-guard/rust-guard/src/lib.rs Update AllowOnlyPolicy deserialization and PolicyContext construction
guards/github-guard/rust-guard/src/labels/mod.rs Re-export new functions; add tests (90 total pass)
internal/config/guard_policy.go Add BlockedUsers/ApprovalLabels to AllowOnlyPolicy and NormalizedGuardPolicy; update marshal/unmarshal/normalize
internal/config/guard_policy_test.go Tests for new fields
internal/guard/wasm.go Update buildStrictLabelAgentPayload to allow new optional keys
internal/guard/wasm_test.go Tests for new keys
guards/github-guard/docs/agentic-workflow-policy.schema.json Add new fields to schema
guards/github-guard/docs/AGENTIC_WORKFLOW_POLICY_FRONTMATTER.md Document new fields
guards/github-guard/docs/GATEWAY_ALLOWONLY_INTEGRATION_SPEC.md Update integration spec

Security Summary

No new security vulnerabilities introduced. CodeQL scan: 0 alerts. The blocked integrity level specifically adds a new denial mechanism — items with blocked:scope tags can never pass the DIFC filter since no agent is assigned that tag.

- Rust guard: add blocked_users/approval_labels to PolicyContext; add
  blocked_integrity(), is_blocked_user(), has_approval_label() helpers;
  apply blocked-user check (new integrity level below none) and
  approval-label promotion in pr_integrity, issue_integrity,
  commit_integrity; update AllowOnlyPolicy deserialization in lib.rs;
  re-export new public functions from labels/mod.rs; add tests

- Go config: add BlockedUsers/ApprovalLabels to AllowOnlyPolicy,
  update UnmarshalJSON/MarshalJSON, validate and deduplicate entries
  in NormalizeGuardPolicy; add to NormalizedGuardPolicy; add tests

- Go wasm: update buildStrictLabelAgentPayload to allow blocked-users
  and approval-labels as optional allow-only keys with validation; add tests

- Schema: add blocked-users and approval-labels to schema.json

- Docs: update AGENTIC_WORKFLOW_POLICY_FRONTMATTER.md and
  GATEWAY_ALLOWONLY_INTEGRATION_SPEC.md

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/e77aee07-131a-47b6-a84f-9908229ff314
…r cleanups

  when blocked-user or approval-label rules fire, logging author/item/reason
- Fix case-sensitive dedup mismatch: NormalizeGuardPolicy now deduplicates
  blocked-users and approval-labels using lowercased keys to match the Rust
  guard's case-insensitive comparison
- Document that commit_integrity skips approval-labels (commits lack labels)
- Improve error message for missing required repos/min-integrity fields
- Add tests for case-insensitive deduplication

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox force-pushed the copilot/implement-blocked-users-and-approval-labels branch from 8e18310 to 3e64f64 Compare March 20, 2026 23:42
@lpcox lpcox marked this pull request as ready for review March 21, 2026 00:00
Copilot AI review requested due to automatic review settings March 21, 2026 00:00
@lpcox lpcox merged commit 3811555 into main Mar 21, 2026
16 checks passed
@lpcox lpcox deleted the copilot/implement-blocked-users-and-approval-labels branch March 21, 2026 00:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements MCP gateway spec additions for GitHub guard policies by introducing an unconditional author blocklist and label-driven integrity promotion, plus the new blocked integrity level to ensure blocked content is always denied by DIFC.

Changes:

  • Add blocked integrity tags (blocked:) and compute effective integrity using blocked-users and approval-labels.
  • Extend gateway policy parsing/normalization and WASM payload validation to accept the new fields.
  • Update schemas/docs and add Go + Rust test coverage for the new behaviors.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/guard/wasm.go Accepts/validates new allow-only keys (blocked-users, approval-labels) in strict payload builder.
internal/guard/wasm_test.go Adds coverage for new allow-only keys and unknown-key rejection.
internal/config/guard_policy.go Extends AllowOnlyPolicy + normalization to include/validate/dedup new fields.
internal/config/guard_policy_test.go Adds tests for parsing/normalization/marshalling of new fields.
guards/github-guard/rust-guard/src/lib.rs Extends allow-only policy deserialization and PolicyContext construction.
guards/github-guard/rust-guard/src/labels/constants.rs Adds BLOCKED_PREFIX / BLOCKED_BASE.
guards/github-guard/rust-guard/src/labels/helpers.rs Implements blocked_integrity, blocked-user checks, and approval-label promotion logic.
guards/github-guard/rust-guard/src/labels/mod.rs Re-exports helpers and adds extensive tests for blocked-users/approval-labels semantics.
guards/github-guard/docs/agentic-workflow-policy.schema.json Adds schema fields for blocked-users and approval-labels.
guards/github-guard/docs/AGENTIC_WORKFLOW_POLICY_FRONTMATTER.md Documents new policy fields and effective integrity computation.
guards/github-guard/docs/GATEWAY_ALLOWONLY_INTEGRATION_SPEC.md Updates integration spec to describe new integrity level and policy fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +409 to +413
// Validate blocked-users if present: must be a non-empty array of non-empty strings.
if blockedUsersRaw, ok := allowOnly["blocked-users"]; ok {
arr, ok := blockedUsersRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("invalid blocked-users value: expected array of strings")
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The inline comment says blocked-users must be a non-empty array, but the code currently allows an empty array (it only validates element types/emptiness). Since other parts of the PR treat an empty list as equivalent to omitting the field, update the comment (or enforce non-empty if that’s truly required) to keep behavior and documentation consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +422 to +426
// Validate approval-labels if present: must be a non-empty array of non-empty strings.
if approvalLabelsRaw, ok := allowOnly["approval-labels"]; ok {
arr, ok := approvalLabelsRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("invalid approval-labels value: expected array of strings")
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The inline comment says approval-labels must be a non-empty array, but this function currently permits an empty array (it only validates element types/emptiness). Please align the comment with the intended semantics (empty list behaves like omitted) or add an explicit non-empty check if empties should be rejected.

Copilot uses AI. Check for mistakes.
Comment on lines +295 to +300
// Validate and normalize blocked-users.
// Dedup uses lowercased keys to match Rust guard's case-insensitive comparison.
if len(policy.AllowOnly.BlockedUsers) > 0 {
seen := make(map[string]struct{}, len(policy.AllowOnly.BlockedUsers))
for _, u := range policy.AllowOnly.BlockedUsers {
u = strings.TrimSpace(u)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

NormalizeGuardPolicy claims to produce a canonical representation for caching/observability, but BlockedUsers/ApprovalLabels are deduplicated without being sorted or otherwise canonicalized. That means semantically identical policies can normalize to different JSON depending on input order. Consider lowercasing + sorting these lists (similar to ScopeValues) after trimming/dedup to make normalization deterministic.

Copilot uses AI. Check for mistakes.
Comment on lines 8 to +12
- New required policy field `Integrity`
- New integrity hierarchy with baseline `none`
- New integrity hierarchy with baseline `none` and `blocked` level below `none`
- Guard-side policy initialization via a new `label_agent` interface
- Blocked-user enforcement via `blocked-users`
- Label-based integrity promotion via `approval-labels`
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This section still states the required policy field is Integrity, but the examples and implementation use min-integrity (and the PR adds fields under that same allow-only shape). Please update the doc to consistently use the correct field/key names throughout (including later label_agent payload examples) to avoid integrators wiring the wrong JSON keys.

Copilot uses AI. Check for mistakes.
lpcox added a commit that referenced this pull request Mar 21, 2026
…bels to README (#2250)

## Changes

Updates the README Guard Policies section to document the new integrity
features:

### Added
- **`blocked-users`** option — array of usernames whose content gets
unconditional `blocked` integrity (below `none`)
- **`approval-labels`** option — array of labels that elevate items to
`approved` integrity (human-review gate)
- **`blocked` integrity level** in the `min-integrity` hierarchy
- Example config showing both options in context
- Link to the [Integrity Filtering
Reference](https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/integrity.md)
in the Further Reading table

### Improved
- Reordered integrity levels from highest to lowest for clarity
- Added notes on private repo items and trusted bots qualifying as
`approved`

### Related
- PR #2241 implements blocked-users and approval-labels in the GitHub
guard
- PR #22044 (gh-aw) adds the Integrity Filtering Reference documentation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants