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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ DEBUG_COLORS=0 DEBUG=* ./awmg --config config.toml
- `MCP_GATEWAY_ALLOWONLY_SCOPE_OWNER` - AllowOnly owner scope value (sets default for `--allowonly-scope-owner`)
- `MCP_GATEWAY_ALLOWONLY_SCOPE_REPO` - AllowOnly repo name, requires owner (sets default for `--allowonly-scope-repo`)
- `MCP_GATEWAY_ALLOWONLY_MIN_INTEGRITY` - AllowOnly integrity level: `none`, `unapproved`, `approved`, `merged` (sets default for `--allowonly-min-integrity`)
- `MCP_GATEWAY_FORCE_PUBLIC_REPOS` - When `true` (default), automatically forces `repos="public"` allow-only policy when `GITHUB_REPOSITORY` identifies a public repository; set to `false` to opt out. Overridden by `gateway.forcePublicRepos` in JSON stdin config.
- `MCP_GATEWAY_TLS_CERT` - Path to TLS server certificate PEM file; enables HTTPS when set together with `MCP_GATEWAY_TLS_KEY` (sets default for `--tls-cert`)
- `MCP_GATEWAY_TLS_KEY` - Path to TLS server private key PEM file; required when `MCP_GATEWAY_TLS_CERT` is set (sets default for `--tls-key`)
- `MCP_GATEWAY_CA_CERT` - Path to CA certificate PEM file for client certificate verification; enables mutual TLS (mTLS) when set alongside `MCP_GATEWAY_TLS_CERT`/`MCP_GATEWAY_TLS_KEY` (sets default for `--tls-ca`)
Expand Down
8 changes: 8 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ tool_timeout = 120
# Prevents remote servers from expiring idle sessions by sending periodic pings.
# Set to -1 to disable keepalive pings entirely.
# keepalive_interval = 1500

# Force repos="public" allow-only policy when the workflow repo is public (default: enabled)
# When enabled (default), the gateway reads GITHUB_REPOSITORY and calls the GitHub API at
# startup; if the repo is public, it overrides the allow-only guard policy for all servers to
# repos="public", preventing agents from reading private repository data.
# Set to false to opt out (e.g., when private-to-public-flows: allow is set in workflow front-matter).
# Can also be controlled via MCP_GATEWAY_FORCE_PUBLIC_REPOS environment variable.
# force_public_repos = true
#
# OpenTelemetry TOML key migration:
# - Prefer [gateway.opentelemetry] for new configs.
Expand Down
1 change: 1 addition & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ The `customSchemas` top-level field allows you to define custom server types bey
| `payloadPathPrefix` (JSON stdin) / `payload_path_prefix` (TOML) | Optional path prefix used when returning `payloadPath` values to clients (for example when the host payload directory is mounted at a different in-container path) | (empty - use actual filesystem path) |
| `payloadSizeThreshold` (JSON) / `payload_size_threshold` (TOML) | Size threshold in bytes; responses larger than this are stored to disk and returned as a `payloadPath` reference | `524288` (512 KB) |
| `trustedBots` (JSON) / `trusted_bots` (TOML) | Optional list of additional bot usernames to trust with "approved" integrity level. Additive to the built-in trusted bot list. When specified, must be a non-empty array with non-empty string entries (spec §4.1.3.4); omit the field entirely if not needed. Example: `["my-bot[bot]", "org-automation"]` | (disabled) |
| `forcePublicRepos` (JSON) / `force_public_repos` (TOML) | When `true` (or omitted), the gateway checks `GITHUB_REPOSITORY` and the GitHub API at startup; if the workflow repository is public, it overrides the allow-only guard policy to `repos="public"` for all servers, preventing agents from reading private repository data. Set to `false` to opt out (equivalent to `private-to-public-flows: allow` in workflow front-matter). Has no effect if `GITHUB_REPOSITORY` is unset or the token is unavailable. Overrides env var `MCP_GATEWAY_FORCE_PUBLIC_REPOS`. | `true` (enabled) |
| `keepaliveInterval` (JSON) / `keepalive_interval` (TOML) | Interval (seconds) between keepalive pings sent to HTTP backends. Prevents remote servers from expiring idle sessions. Set to `-1` to disable keepalive pings entirely. | `1500` (25 min) |

### OpenTelemetry / Tracing
Expand Down
16 changes: 16 additions & 0 deletions internal/config/config_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ type GatewayConfig struct {
// Example values: "copilot-swe-agent[bot]", "my-org-bot[bot]"
TrustedBots []string `toml:"trusted_bots" json:"trusted_bots,omitempty"`

// ForcePublicRepos controls whether the gateway automatically overrides the
// allow-only policy to repos="public" when the GITHUB_REPOSITORY repo is public.
// nil / omitted → enabled by default (auto-force when repo is public)
// true → explicitly enabled
// false → disabled (opt-out via private-to-public-flows: allow)
// Corresponds to env var MCP_GATEWAY_FORCE_PUBLIC_REPOS.
ForcePublicRepos *bool `toml:"force_public_repos" json:"forcePublicRepos,omitempty"`

// SinkVisibilityExemptServers lists server IDs that are exempt from the
// default sink-visibility="public" enforcement. By default, all non-safe-outputs
// write-sink servers are assigned sink-visibility="public" (security-by-default).
// Servers in this list retain their configured (or omitted) sink-visibility as-is.
// Use ["*"] to exempt all servers (equivalent to disabling the default).
// Set by the compiler when private-to-public-flows is configured in frontmatter.
SinkVisibilityExemptServers []string `toml:"sink_visibility_exempt_servers" json:"sinkVisibilityExemptServers,omitempty"`

// Tracing holds OpenTelemetry OTLP tracing configuration (legacy TOML key).
// New configurations should use the opentelemetry key (spec §4.1.3.6).
// When Endpoint is set, traces are exported to the specified OTLP endpoint.
Expand Down
32 changes: 20 additions & 12 deletions internal/config/config_stdin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ type StdinConfig struct {
// StdinGatewayConfig represents gateway configuration in stdin JSON format.
// Uses pointers for optional fields to distinguish between unset and zero values.
type StdinGatewayConfig struct {
Port *int `json:"port,omitempty"`
AgentID string `json:"agentId,omitempty"`
APIKey string `json:"apiKey,omitempty"`
Domain string `json:"domain,omitempty"`
StartupTimeout *int `json:"startupTimeout,omitempty"`
ToolTimeout *int `json:"toolTimeout,omitempty"`
KeepaliveInterval *int `json:"keepaliveInterval,omitempty"`
PayloadDir string `json:"payloadDir,omitempty"`
PayloadPathPrefix *string `json:"payloadPathPrefix,omitempty"`
PayloadSizeThreshold *int `json:"payloadSizeThreshold,omitempty"`
TrustedBots []string `json:"trustedBots,omitempty"`
OpenTelemetry *StdinOpenTelemetryConfig `json:"opentelemetry,omitempty"`
Port *int `json:"port,omitempty"`
AgentID string `json:"agentId,omitempty"`
APIKey string `json:"apiKey,omitempty"`
Domain string `json:"domain,omitempty"`
StartupTimeout *int `json:"startupTimeout,omitempty"`
ToolTimeout *int `json:"toolTimeout,omitempty"`
KeepaliveInterval *int `json:"keepaliveInterval,omitempty"`
PayloadDir string `json:"payloadDir,omitempty"`
PayloadPathPrefix *string `json:"payloadPathPrefix,omitempty"`
PayloadSizeThreshold *int `json:"payloadSizeThreshold,omitempty"`
TrustedBots []string `json:"trustedBots,omitempty"`
ForcePublicRepos *bool `json:"forcePublicRepos,omitempty"`
SinkVisibilityExemptServers []string `json:"sinkVisibilityExemptServers,omitempty"`
OpenTelemetry *StdinOpenTelemetryConfig `json:"opentelemetry,omitempty"`

agentIDSet bool `json:"-"`
legacyAPIKeySet bool `json:"-"`
Expand Down Expand Up @@ -419,6 +421,12 @@ func convertStdinConfig(stdinCfg *StdinConfig) (*Config, error) {
}
cfg.Gateway.TrustedBots = stdinCfg.Gateway.TrustedBots
}
if stdinCfg.Gateway.ForcePublicRepos != nil {
cfg.Gateway.ForcePublicRepos = stdinCfg.Gateway.ForcePublicRepos
}
if len(stdinCfg.Gateway.SinkVisibilityExemptServers) > 0 {
cfg.Gateway.SinkVisibilityExemptServers = stdinCfg.Gateway.SinkVisibilityExemptServers
}
} else {
logStdin.Print("No gateway config in stdin, applying defaults")
cfg.Gateway = &GatewayConfig{}
Expand Down
3 changes: 3 additions & 0 deletions internal/config/guard_policy_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const (
EnvAllowOnlyScopeOwner = "MCP_GATEWAY_ALLOWONLY_SCOPE_OWNER"
EnvAllowOnlyScopeRepo = "MCP_GATEWAY_ALLOWONLY_SCOPE_REPO"
EnvAllowOnlyMinIntegrity = "MCP_GATEWAY_ALLOWONLY_MIN_INTEGRITY"
// EnvForcePublicRepos controls whether force-public-repos enforcement is active.
// Default true (feature on). Set to "false" to opt out.
EnvForcePublicRepos = "MCP_GATEWAY_FORCE_PUBLIC_REPOS"
)

// ParseServerGuardPolicy parses a guard policy from a server-specific raw policy map.
Expand Down
64 changes: 59 additions & 5 deletions internal/config/schema/mcp-gateway-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@
"description": "Map of custom server type names to JSON Schema URLs for validation. Custom types enable extensibility for specialized MCP server implementations. Keys are type names (must not be 'stdio' or 'http'), values are HTTPS URLs pointing to JSON Schema definitions, or empty strings to skip validation.",
"propertyNames": {
"allOf": [
{ "pattern": "^[a-z][a-z0-9-]*$" },
{ "not": { "enum": ["stdio", "http"] } }
{
"pattern": "^[a-z][a-z0-9-]*$"
},
{
"not": {
"enum": [
"stdio",
"http"
]
}
}
]
},
"patternProperties": {
Expand Down Expand Up @@ -162,7 +171,12 @@
},
"guard-policies": {
"type": "object",
"description": "Guard policies for access control at the MCP gateway level. The structure of guard policies is server-specific.",
"description": "Guard policies for access control at the MCP gateway level. Supports a 'write-sink' policy for DIFC-based output filtering. Additional server-specific policies may be provided.",
"properties": {
"write-sink": {
"$ref": "#/definitions/writeSinkGuardPolicyConfig"
}
},
"additionalProperties": true
}
},
Expand Down Expand Up @@ -217,7 +231,12 @@
},
"guard-policies": {
"type": "object",
"description": "Guard policies for access control at the MCP gateway level. The structure of guard policies is server-specific. For GitHub MCP server, see the GitHub guard policy schema. For other servers (Jira, WorkIQ), different policy schemas will apply.",
"description": "Guard policies for access control at the MCP gateway level. Supports a 'write-sink' policy for DIFC-based output filtering. Additional server-specific policies may be provided.",
"properties": {
"write-sink": {
"$ref": "#/definitions/writeSinkGuardPolicyConfig"
}
},
"additionalProperties": true
},
"auth": {
Expand Down Expand Up @@ -294,7 +313,12 @@
"type": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$",
"not": {"enum": ["stdio", "http"]},
"not": {
"enum": [
"stdio",
"http"
]
},
"description": "Custom server type name. Must not be 'stdio' or 'http'. Must be registered in customSchemas."
}
},
Expand Down Expand Up @@ -397,6 +421,16 @@
"opentelemetry": {
"$ref": "#/definitions/opentelemetryConfig",
"description": "Optional OpenTelemetry configuration for emitting distributed tracing spans for MCP calls. When configured, the gateway exports OTLP/HTTP traces to the specified collector endpoint."
},
"forcePublicRepos": {
"type": "boolean",
"description": "When true (default), forces the allow-only policy to repos=\"public\" at runtime if the gateway detects it is running in a public repository. Set to false by the compiler when private-to-public-flows: allow is declared in workflow frontmatter, or via MCP_GATEWAY_FORCE_PUBLIC_REPOS=false environment variable. See MCP Gateway Specification section 4.1.3.8.",
"default": true
},
"sinkVisibilityExemptServers": {
"type": "array",
"items": { "type": "string" },
"description": "Server IDs exempt from the default sink-visibility=\"public\" enforcement. By default, all non-safe-outputs write-sink servers are assigned sink-visibility=\"public\" (security-by-default). Servers listed here retain their configured (or omitted) sink-visibility as-is. Use [\"*\"] to exempt all servers. Set by the compiler when private-to-public-flows is configured in workflow frontmatter."
}
},
"required": [
Expand All @@ -417,6 +451,26 @@
}
]
},
"writeSinkGuardPolicyConfig": {
"type": "object",
"description": "Write-sink guard policy for DIFC-based output filtering. Controls whether an agent may write to the safe-outputs sink based on the agent's accumulated secrecy tags and the target repository visibility. Per MCP Gateway Specification section 10.8.",
"properties": {
"accept": {
"type": "array",
"description": "Secrecy tag patterns that are permitted to write to this sink. Use [\"*\"] to accept all secrecy levels. Required for all write-sink policies. When sink-visibility is \"public\", this field is syntactically required but has no runtime effect — resource secrecy is unconditionally set to empty.",
"items": {
"type": "string"
}
},
"sink-visibility": {
"type": "string",
"description": "Declares the visibility of the safe-outputs target repository (always the workflow's own repo, i.e. GITHUB_REPOSITORY). When \"public\", agents with non-empty secrecy are blocked regardless of accept patterns. When \"private\" or \"internal\", standard accept-pattern matching applies. When omitted, backward-compatible accept-pattern matching applies.",
"enum": ["public", "private", "internal"]
}
},
"required": ["accept"],
"additionalProperties": false
},
"opentelemetryConfig": {
"type": "object",
"description": "OpenTelemetry configuration for the MCP Gateway. When present, the gateway emits distributed tracing spans for each MCP tool invocation and exports them via OTLP/HTTP to the configured collector endpoint. Per MCP Gateway Specification section 4.1.3.6.",
Expand Down
Loading
Loading