Skip to content

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

Merged
lpcox merged 2 commits into
mainfrom
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 into
mainfrom
copilot/implement-blocked-users-and-approval-labels

Conversation

CopilotAI commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

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

FileChange
guards/github-guard/rust-guard/src/labels/constants.rsAdd BLOCKED_PREFIX/BLOCKED_BASE
guards/github-guard/rust-guard/src/labels/helpers.rsAdd 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.rsUpdate AllowOnlyPolicy deserialization and PolicyContext construction
guards/github-guard/rust-guard/src/labels/mod.rsRe-export new functions; add tests (90 total pass)
internal/config/guard_policy.goAdd BlockedUsers/ApprovalLabels to AllowOnlyPolicy and NormalizedGuardPolicy; update marshal/unmarshal/normalize
internal/config/guard_policy_test.goTests for new fields
internal/guard/wasm.goUpdate buildStrictLabelAgentPayload to allow new optional keys
internal/guard/wasm_test.goTests for new keys
guards/github-guard/docs/agentic-workflow-policy.schema.jsonAdd new fields to schema
guards/github-guard/docs/AGENTIC_WORKFLOW_POLICY_FRONTMATTER.mdDocument new fields
guards/github-guard/docs/GATEWAY_ALLOWONLY_INTEGRATION_SPEC.mdUpdate 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
lpcoxforce-pushed the copilot/implement-blocked-users-and-approval-labels branch from 8e18310 to 3e64f64CompareMarch 20, 2026 23:42
@lpcox
lpcox marked this pull request as ready for review March 21, 2026 00:00
CopilotAI review requested due to automatic review settings March 21, 2026 00:00
@lpcox
lpcox merged commit 3811555 into mainMar 21, 2026
16 checks passed
@lpcox
lpcox deleted the copilot/implement-blocked-users-and-approval-labels branch March 21, 2026 00:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
internal/guard/wasm.goAccepts/validates new allow-only keys (blocked-users, approval-labels) in strict payload builder.
internal/guard/wasm_test.goAdds coverage for new allow-only keys and unknown-key rejection.
internal/config/guard_policy.goExtends AllowOnlyPolicy + normalization to include/validate/dedup new fields.
internal/config/guard_policy_test.goAdds tests for parsing/normalization/marshalling of new fields.
guards/github-guard/rust-guard/src/lib.rsExtends allow-only policy deserialization and PolicyContext construction.
guards/github-guard/rust-guard/src/labels/constants.rsAdds BLOCKED_PREFIX / BLOCKED_BASE.
guards/github-guard/rust-guard/src/labels/helpers.rsImplements blocked_integrity, blocked-user checks, and approval-label promotion logic.
guards/github-guard/rust-guard/src/labels/mod.rsRe-exports helpers and adds extensive tests for blocked-users/approval-labels semantics.
guards/github-guard/docs/agentic-workflow-policy.schema.jsonAdds schema fields for blocked-users and approval-labels.
guards/github-guard/docs/AGENTIC_WORKFLOW_POLICY_FRONTMATTER.mdDocuments new policy fields and effective integrity computation.
guards/github-guard/docs/GATEWAY_ALLOWONLY_INTEGRATION_SPEC.mdUpdates 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")

CopilotAIMar 21, 2026

Copy link

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")

CopilotAIMar 21, 2026

Copy link

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)

CopilotAIMar 21, 2026

Copy link

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`

CopilotAIMar 21, 2026

Copy link

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
@github-actionsgithub-actionsBot mentioned this pull request Mar 23, 2026
4 tasks
Sign up for freeto 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

@lpcox