feat: enforce repos="public" when workflow runs in a public repository#8933
Merged
Conversation
- Add ForcePublicRepos *bool config field to GatewayConfig and StdinGatewayConfig - Add EnvForcePublicRepos constant (MCP_GATEWAY_FORCE_PUBLIC_REPOS) - Update JSON schema with forcePublicRepos property - Add caching fields to UnifiedServer for shared repo visibility - Implement resolveWorkflowRepoVisibility() shared cache for both checks - Implement shouldForcePublicRepos(), computeForcePublicRepos(), overrideToPublicScope() - Override allow-only policy to repos=public in registerGuard() for public repos - Add unit tests (14 tests) in force_public_repos_test.go - Add integration tests in test/integration/force_public_repos_test.go - Update docs/CONFIGURATION.md with forcePublicRepos field documentation - Add MCP_GATEWAY_FORCE_PUBLIC_REPOS to AGENTS.md environment variables - Add commented example to config.example.toml Closes #8932
…, rename test funcs
Copilot
AI
changed the title
[WIP] Enforce repos="public" when workflow runs in a public repository
feat: enforce repos="public" when workflow runs in a public repository
Jul 8, 2026
…d default - Add writeSinkGuardPolicyConfig definition with typed accept/sink-visibility - Add typed write-sink property to guard-policies in both server configs - Add "default": true to forcePublicRepos schema field - Update description to match spec PR (gh-aw#44305) - Update integration tests: invalid sink-visibility now rejected by schema validation (exits with error) rather than graceful fallback - Remove case-insensitive integration test (schema enforces lowercase enum) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens DIFC allow-only policy behavior for workflows running in public repositories by detecting repository visibility at runtime and force-scoping reads to repos="public" (with an opt-out), closing a potential private-data read/exfiltration path.
Changes:
- Adds
gateway.forcePublicRepos/MCP_GATEWAY_FORCE_PUBLIC_REPOSconfiguration and schema/docs support (default enabled). - Implements runtime repo-visibility caching and applies an allow-only
repos="public"override during guard registration when the workflow repo is public. - Adds unit + integration tests for the new force-public behavior, and updates sink-visibility tests to reflect stricter schema validation.
Show a summary per file
| File | Description |
|---|---|
| test/integration/sink_visibility_test.go | Updates sink-visibility integration tests to expect schema rejection for invalid values. |
| test/integration/force_public_repos_test.go | Adds integration coverage for force-public-repos override and opt-out scenarios. |
| internal/server/unified.go | Adds per-server cached fields for workflow repo visibility and force-public-repos decision. |
| internal/server/guard_init.go | Implements cached visibility lookup, sink-visibility runtime verification changes, and allow-only override to repos="public". |
| internal/server/force_public_repos_test.go | Adds unit tests for shouldForcePublicRepos/overrideToPublicScope behaviors. |
| internal/config/schema/mcp-gateway-config.schema.json | Adds gateway.forcePublicRepos and a stricter schema for guard-policies.write-sink including sink-visibility enum validation. |
| internal/config/guard_policy_parse.go | Introduces EnvForcePublicRepos constant for runtime feature gating. |
| internal/config/config_stdin.go | Adds stdin JSON support for gateway.forcePublicRepos. |
| internal/config/config_core.go | Adds ForcePublicRepos *bool to GatewayConfig (TOML + JSON). |
| docs/CONFIGURATION.md | Documents forcePublicRepos semantics and defaults. |
| config.example.toml | Documents TOML force_public_repos knob and intent. |
| AGENTS.md | Documents MCP_GATEWAY_FORCE_PUBLIC_REPOS environment variable. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 12/12 changed files
- Comments generated: 6
- Review effort level: Low
Comment on lines
+493
to
+504
| // Config opt-out: forcePublicRepos=false in gateway config disables the feature. | ||
| if us.cfg != nil && us.cfg.Gateway != nil && us.cfg.Gateway.ForcePublicRepos != nil && !*us.cfg.Gateway.ForcePublicRepos { | ||
| logGuardInit.Print("shouldForcePublicRepos: disabled by config (forcePublicRepos=false)") | ||
| return false | ||
| } | ||
|
|
||
| // Env opt-out: MCP_GATEWAY_FORCE_PUBLIC_REPOS=false disables the feature. | ||
| // Default is true (feature enabled), so only an explicit "false" disables it. | ||
| if !envutil.GetEnvBool(config.EnvForcePublicRepos, true) { | ||
| logGuardInit.Printf("shouldForcePublicRepos: disabled by env var %s=false", config.EnvForcePublicRepos) | ||
| return false | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…y net - Non-safe-outputs write-sink servers default to sink-visibility="public" (security-by-default: assume external sinks release data publicly) - Add sinkVisibilityExemptServers config field for targeted opt-out - Add runtime safety net for safe-outputs: if compiler omits sink-visibility but workflow repo is public, gateway forces "public" to prevent exfiltration - Add isSafeOutputsServer() recognizing both canonical and legacy forms - Add validateSinkVisibilityExemptServers() with unknown-server-ID warnings - Update JSON schema with sinkVisibilityExemptServers field - Add integration tests for default visibility and exempt server scenarios Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the gateway runs in a public repository, agents could accumulate private-repo secrecy tags via allow-only policies broader than
repos="public", exposing a GitLost attack vector. This adds a runtime check that auto-forcesrepos="public"on all server guard policies whenGITHUB_REPOSITORYidentifies a public repo.Config
New
forcePublicReposgateway field (MCP_GATEWAY_FORCE_PUBLIC_REPOSenv var), enabled by default. Set tofalseto opt out (compiler emits this forprivate-to-public-flows: allow):{ "gateway": { "forcePublicRepos": false } }Implementation
internal/config/—ForcePublicRepos *booladded toGatewayConfigandStdinGatewayConfig;EnvForcePublicReposconstant; JSON schema updatedinternal/server/unified.go— caching fields (repoVisibilityOnce/Cached/CacheOK,forcePublicReposOnce/Result) onUnifiedServerinternal/server/guard_init.go:resolveWorkflowRepoVisibility()— singlesync.OnceAPI call shared by bothverifySinkVisibilityAtRuntimeand the new force-public check (no double API call)shouldForcePublicRepos()/computeForcePublicRepos()— check config opt-out, env opt-out,GITHUB_REPOSITORYpresence, token availability, then API visibilityoverrideToPublicScope()— patches both globalcfg.GuardPolicyand per-serverGuardPoliciestorepos="public"; skips write-sink-only policies (mutually exclusive with allow-only)registerGuard()— calls override beforerequireGuardPolicyIfGuardEnabled, so the patched value is the one persistedBehavior
repos="all"repos="public", warning loggedforcePublicRepos: falsein configGITHUB_REPOSITORYunset or token missingTests
internal/server/force_public_repos_test.gotest/integration/force_public_repos_test.gocovering public override, private no-op, config opt-out, and missingGITHUB_REPOSITORY