Skip to content

cleanup: remove guard CLI flags, auto-detect DIFC from policies#1723

Merged
lpcox merged 2 commits intomainfrom
cleanup/guard-flags
Mar 10, 2026
Merged

cleanup: remove guard CLI flags, auto-detect DIFC from policies#1723
lpcox merged 2 commits intomainfrom
cleanup/guard-flags

Conversation

@lpcox
Copy link
Collaborator

@lpcox lpcox commented Mar 10, 2026

Summary

Remove unnecessary guard CLI flags and simplify DIFC configuration. Guards are now auto-enabled when an allow-only policy is detected — no explicit flags needed.

Flags Removed

Flag Env Var Reason
--enable-guards MCP_GATEWAY_ENABLE_GUARDS Auto-detected from policies
--enable-config-extensions MCP_GATEWAY_CONFIG_EXTENSIONS Extension fields always accepted
--session-secrecy MCP_GATEWAY_SESSION_SECRECY Session labels set by guards
--session-integrity MCP_GATEWAY_SESSION_INTEGRITY Session labels set by guards

How DIFC Auto-Detection Works

After guard registration in NewUnified(), the gateway checks:

  1. Does any server have a non-noop guard registered?
  2. Is there a global guard policy override (--guard-policy-json)?
  3. Does any server have per-server guard-policies configured?

If any condition is true, DIFC is automatically enabled. Otherwise, all servers get noop guards and DIFC remains disabled.

Other Changes

  • Remove EnableDIFC field from config.Config and SessionConfig struct
  • Add HasNonNoopGuard() to guard.Registry for auto-detection
  • Add hasServerGuardPolicies() helper to detect per-server guard policies
  • requireGuardPolicyIfGuardEnabled() now warns and falls back to noop instead of fatal error
  • Restore JSON schema validation in LoadFromStdin: extension fields (guards, per-server guard) are stripped from a copy before validation so spec-compliance is maintained while still accepting gateway-specific extensions; unsupported fields like command are properly rejected
  • Fix flaky integration tests (hardcoded ports → dynamic getFreePort(t))
  • Update guard-policies docs to canonical allow-only format
  • Remove all references to removed flags from scripts and docs

Testing

  • make agent-finished passes (format, build, lint, unit + integration tests)
  • 24 files changed

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Remove --enable-guards, --enable-config-extensions, --session-secrecy,
and --session-integrity CLI flags. Guards are now auto-enabled when an
allow-only policy is detected in the configuration.

Changes:
- Remove EnableDIFC field from config.Config
- Remove SessionConfig struct and session label parsing
- Remove enableDIFC, enableConfigExt, sessionSecrecy, sessionIntegrity
  flag variables and their env var defaults
- Auto-detect DIFC in NewUnified() after guard registration: enabled
  when any non-noop guard or global policy override is present
- Add HasNonNoopGuard() to guard.Registry for auto-detection
- Move guards status logging into NewUnified() (before backend launch)
- Always skip strict JSON schema validation for extension fields
- Change requireGuardPolicyIfGuardEnabled to warn+noop fallback
  instead of fatal error
- Fix flaky integration tests: replace hardcoded ports with dynamic
  allocation via getFreePort(t)
- Update guard-policies docs to use canonical allow-only format
- Update scripts to remove all references to removed flags
- Update DIFC_RULES.md, config.example.toml, OVERVIEW.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 04:54
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

This PR simplifies DIFC/guards configuration by removing the legacy “enable guards/config extensions/session labels” flags and shifting DIFC enablement to automatic detection based on configured guards/policies. It also updates tests, scripts, and documentation to align with the new behavior.

Changes:

  • Remove --enable-guards, --enable-config-extensions, and session label flags/env vars; remove related config fields.
  • Auto-detect DIFC enablement in NewUnified() based on registered guards/policy overrides; add registry helper HasNonNoopGuard().
  • Update integration/unit tests and docs/scripts; make integration tests less flaky by using dynamic ports.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/integration/difc_config_test.go Removes coverage for deleted env vars/flags; uses dynamic ports; updates assertions to new auto-enable behavior.
internal/server/unified_test.go Updates tests after removing EnableDIFC; simplifies edge-case cases.
internal/server/unified.go Removes config-driven DIFC enable/session labels; adds DIFC auto-enable logic and policy-required fallback-to-noop behavior.
internal/server/routed_test.go Updates DIFC enabled tests to use guard policy (auto-enable) instead of EnableDIFC.
internal/server/register_tools_from_backend_test.go Removes EnableDIFC from test configs to match new config shape.
internal/server/label_agent_test.go Removes EnableDIFC usage; relies on guard policy/guard-policies to trigger DIFC where needed.
internal/server/call_backend_tool_test.go Removes EnableDIFC usage from test configs.
internal/guard/registry.go Adds HasNonNoopGuard() helper for DIFC auto-detection.
internal/envutil/envutil_test.go Replaces removed env var test (MCP_GATEWAY_ENABLE_GUARDS) with allow-only scope env var.
internal/difc/DIFC_RULES.md Updates CLI/env var docs and examples to reflect auto-enable and allow-only policy format.
internal/config/validation_test.go Adjusts expectations for stdin validation after schema validation behavior change.
internal/config/config_test.go Updates assertions/error expectations related to stdin loading behavior changes.
internal/config/config_stdin.go Removes config-extensions gating and session parsing; skips strict schema validation and logs that choice.
internal/config/config_difc_test.go Removes session label parsing tests; updates extension-field acceptance tests to no longer require a flag.
internal/config/config_core.go Removes EnableDIFC and Gateway.Session/SessionConfig from core config types.
internal/cmd/root.go Removes propagation/validation for removed flags and session label wiring; retains DIFC mode validation and policy override resolution.
internal/cmd/flags_difc_test.go Removes tests for deleted defaults/session label parsing/config extensions helpers.
internal/cmd/flags_difc.go Removes deleted flags and their default/env helpers; keeps DIFC mode/sink/policy flags.
internal/cmd/flags.go Updates documentation comment to reflect current DIFC env default helpers.
guards/github-guard/scripts/run_integration_tests.sh Removes deleted CLI args/env vars from integration script.
guards/github-guard/scripts/run_copilot_test.sh Removes deleted CLI args/env vars from copilot test script modes.
guards/github-guard/docs/OVERVIEW.md Removes deleted env vars from docs examples.
config.example.toml Updates guard-policies example to canonical allow-only key and removes enable_guards mention.
README.md Updates guard-policies examples/docs to canonical allow-only format.
Comments suppressed due to low confidence (2)

internal/server/unified_test.go:188

  • TestRequireSession still claims to cover behavior “even with DIFC enabled”, but the test config no longer enables DIFC (no policy / non-noop guard), so it’s currently exercising the disabled/default path and overlaps with TestRequireSession_DifcDisabled. Consider either (a) updating the assertions/messages to not mention DIFC, or (b) enabling DIFC in this test via a guard policy so both enabled/disabled cases remain covered.
func TestRequireSession(t *testing.T) {
	cfg := &config.Config{
		Servers: map[string]*config.ServerConfig{},
	}

	ctx := context.Background()
	us, err := NewUnified(ctx, cfg)
	require.NoError(t, err, "NewUnified() failed")
	defer us.Close()

	// Create a session
	sessionID := "valid-session"
	us.sessionMu.Lock()
	us.sessions[sessionID] = NewSession(sessionID, "token")
	us.sessionMu.Unlock()

	// Test with valid session
	ctxWithSession := context.WithValue(ctx, SessionIDContextKey, sessionID)
	err = us.requireSession(ctxWithSession)
	assert.NoError(t, err, "requireSession() failed for valid session")

	// Test with non-existent session - should auto-create even with DIFC enabled
	ctxWithNewSession := context.WithValue(ctx, SessionIDContextKey, "new-session")
	err = us.requireSession(ctxWithNewSession)
	require.NoError(t, err, "requireSession() should auto-create session even when DIFC is enabled")

internal/server/unified.go:195

  • When logging the enabled guards mode, this uses cfg.DIFCMode directly; in tests/other call sites DIFCMode can be empty which results in a blank mode in logs even though strict is the effective default. Consider logging the resolved/effective mode (e.g., defaulting to strict when empty, or using the evaluator’s mode).
	// Log guards status early (before backend launch which may take time)
	if us.enableDIFC {
		log.Printf("Guards enforcement enabled with mode: %s", cfg.DIFCMode)
	} else {

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

Comment on lines 187 to 191
// Validate against JSON schema (fail-fast, spec-compliant)
// Skip schema validation when config extensions are enabled, as extensions like
// "guards", "guard", and "session" are not in the official schema yet.
if isConfigExtensionsEnabled() {
logConfig.Print("Config extensions enabled - skipping strict JSON schema validation")
log.Println("Config extensions enabled - schema validation relaxed for extension fields")
} else {
if err := validateJSONSchema(data); err != nil {
return nil, err
}
}
// Schema validation is skipped because extension fields like "guards", "guard",
// and "guard-policies" are not in the official schema yet.
logConfig.Print("Skipping strict JSON schema validation (extension fields may be present)")

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

LoadFromStdin now skips JSON schema validation unconditionally. This makes unsupported/typo fields silently accepted (e.g., a command field on stdio servers), which can lead to confusing failures later and reduces spec compliance. To keep extension-field support without dropping validation entirely, consider stripping known extension fields (guards/guard/guard-policies/etc.) from a copy of the JSON before schema validation, or explicitly validating/rejecting unknown fields for standard server types while still allowing known extensions.

Copilot uses AI. Check for mistakes.
Comment on lines 644 to 660
name: "command field not supported",
config: `{
"mcpServers": {
"test": {
"type": "stdio",
"command": "node",
"container": "test:latest"
}
},
"gateway": {
"port": 8080,
"domain": "localhost",
"apiKey": "test-key"
}
}`,
shouldErr: true,
errorMsg: "validation error",
shouldErr: false,
},
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This case is named “command field not supported” but now expects no error, which codifies silent acceptance of an unsupported field. If the intent is to allow only specific extension fields, consider either rejecting command explicitly (with a targeted error) or renaming the test to reflect the new behavior (e.g., “unknown fields ignored”).

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +190
// Auto-enable DIFC if any non-noop guard was registered or a global policy override exists
if !us.enableDIFC && (us.guardRegistry.HasNonNoopGuard() || cfg.GuardPolicy != nil) {
us.enableDIFC = true
logUnified.Printf("Auto-enabled DIFC: non-noop guard or policy detected")
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

Auto-enable logic only checks cfg.GuardPolicy (global override) or HasNonNoopGuard(), but does not consider per-server guard-policies when no non-noop guard ends up registered (e.g., guard missing/failed to load). This diverges from the PR description (“--guard-policy-json or per-server guard-policies”) and can leave DIFC disabled even though policies are present. Consider detecting policies by scanning cfg.Servers[*].GuardPolicies (or calling resolveGuardPolicy per server) and enabling DIFC if any resolve to a non-nil policy.

This issue also appears on line 192 of the same file.

Copilot uses AI. Check for mistakes.
@lpcox
Copy link
Collaborator Author

lpcox commented Mar 10, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

… DIFC auto-detection

- Restore JSON schema validation in LoadFromStdin by stripping known
  extension fields (guards, per-server guard) before validating against
  the upstream schema; command field is now properly rejected again
- Add 3 positive test cases verifying guard/guards/guard-policies pass
- Restore original errorMsg values in tests that were loosened when
  schema validation was disabled (InvalidMountFormat, DirectCommand,
  invalid gateway port, command field not supported)
- Add hasServerGuardPolicies() helper and use it in DIFC auto-detection
  so per-server guard-policies enable DIFC even without a non-noop guard

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Mar 10, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Applied all three changes from the review thread in commit 5d5c56a:

  1. Schema validation restored (config_stdin.go): LoadFromStdin now strips known extension fields (guards top-level, per-server guard) from a copy of the JSON before running validateJSONSchema. This rejects unsupported fields like command while still allowing gateway-specific extensions to pass.

  2. Tests updated (validation_test.go, config_test.go): Restored "command field not supported" to shouldErr: true, restored error message expectations to "validation error" for schema-caught cases (mount format, gateway port, DirectCommand), and added 3 positive test cases verifying guard, guards, and guard-policies extension fields are accepted.

  3. DIFC auto-detection extended (unified.go): Added hasServerGuardPolicies() helper that scans cfg.Servers[*].GuardPolicies. DIFC is now auto-enabled when any server has per-server guard policies configured, even if no non-noop guard was registered.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build1683151385/b336/launcher.test /tmp/go-build1683151385/b336/launcher.test -test.testlogfile=/tmp/go-build1683151385/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s 8217�� 64/src/net lang.org/x/net/http2/hpack/encode.go x_amd64/vet -I bis -I x_amd64/vet -V=f�� _.a -D x_amd64/vet -D bidirule -gensymabis x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build4104993166/b336/launcher.test /tmp/go-build4104993166/b336/launcher.test -test.testlogfile=/tmp/go-build4104993166/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s go1.25.7 -c=4 -nolocalimports -importcfg /tmp/go-build4104993166/b335/importcfg -pack /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/connection_pool.go /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/launcher.go /usr�� stdin.go y bash ntime.v2.task/mo/opt/hostedtoolcache/go/1.25.7/x64/pkg/tool/linux_amd64/vet x_amd64/vet ntime.v2.task/mo-bool 965/log.json (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build1683151385/b318/config.test /tmp/go-build1683151385/b318/config.test -test.testlogfile=/tmp/go-build1683151385/b318/testlog.txt -test.paniconexit0 -test.timeout=10m0s -c1 ache/go/1.25.7/x64/src/runtime/c-c=4 (dns block)
    • Triggering command: /tmp/go-build3678863067/b001/config.test /tmp/go-build3678863067/b001/config.test -test.testlogfile=/tmp/go-build3678863067/b001/testlog.txt -test.paniconexit0 -test.timeout=2m0s (dns block)
    • Triggering command: /tmp/go-build3678905790/b318/config.test /tmp/go-build3678905790/b318/config.test -test.testlogfile=/tmp/go-build3678905790/b318/testlog.txt -test.paniconexit0 -test.timeout=10m0s /usr�� --version 64/pkg/tool/linu-buildtags ker/cli-plugins/docker-compose _.a 821794/b100/ x_amd64/vet d79/log.json n-me�� --root /var/run/docker/runtime-runc/moby /usr/lib/open-iscsi/net-interface-handler /run/containerd/docker --log-format json 3bd/log.json (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build1683151385/b336/launcher.test /tmp/go-build1683151385/b336/launcher.test -test.testlogfile=/tmp/go-build1683151385/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s 8217�� 64/src/net lang.org/x/net/http2/hpack/encode.go x_amd64/vet -I bis -I x_amd64/vet -V=f�� _.a -D x_amd64/vet -D bidirule -gensymabis x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build4104993166/b336/launcher.test /tmp/go-build4104993166/b336/launcher.test -test.testlogfile=/tmp/go-build4104993166/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s go1.25.7 -c=4 -nolocalimports -importcfg /tmp/go-build4104993166/b335/importcfg -pack /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/connection_pool.go /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/launcher.go /usr�� stdin.go y bash ntime.v2.task/mo/opt/hostedtoolcache/go/1.25.7/x64/pkg/tool/linux_amd64/vet x_amd64/vet ntime.v2.task/mo-bool 965/log.json (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build1683151385/b336/launcher.test /tmp/go-build1683151385/b336/launcher.test -test.testlogfile=/tmp/go-build1683151385/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s 8217�� 64/src/net lang.org/x/net/http2/hpack/encode.go x_amd64/vet -I bis -I x_amd64/vet -V=f�� _.a -D x_amd64/vet -D bidirule -gensymabis x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build4104993166/b336/launcher.test /tmp/go-build4104993166/b336/launcher.test -test.testlogfile=/tmp/go-build4104993166/b336/testlog.txt -test.paniconexit0 -test.timeout=10m0s go1.25.7 -c=4 -nolocalimports -importcfg /tmp/go-build4104993166/b335/importcfg -pack /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/connection_pool.go /home/REDACTED/work/gh-aw-mcpg/gh-aw-mcpg/internal/launcher/launcher.go /usr�� stdin.go y bash ntime.v2.task/mo/opt/hostedtoolcache/go/1.25.7/x64/pkg/tool/linux_amd64/vet x_amd64/vet ntime.v2.task/mo-bool 965/log.json (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build1683151385/b345/mcp.test /tmp/go-build1683151385/b345/mcp.test -test.testlogfile=/tmp/go-build1683151385/b345/testlog.txt -test.paniconexit0 -test.timeout=10m0s rtcf�� _.a zm5GNif4_ x_amd64/vet --gdwarf-5 ternal/engine/wainfo -o x_amd64/vet OMGb�� ache/go/1.25.7/xgo1.25.7 .cfg x_amd64/vet bis -Wl,--no-gc-sect/usr/bin/runc -fmessage-length--version x_amd64/vet (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@lpcox lpcox merged commit f1e16c6 into main Mar 10, 2026
6 checks passed
@lpcox lpcox deleted the cleanup/guard-flags branch March 10, 2026 13:55
lpcox added a commit that referenced this pull request Mar 10, 2026
## Summary

Adds a daily agentic workflow (`smoke-allowonly.md`) that validates
AllowOnly guard policy enforcement through the MCP Gateway.

## Guard Policy
- **repos**: `["github/gh-aw*"]` (wildcard match)
- **min-integrity**: `approved` (OWNER, MEMBER, COLLABORATOR only)

## Test Coverage

### In-Scope Access (expected: allowed)
- `list_issues`, `list_pull_requests`, `list_commits`,
`get_file_contents`, `list_branches`, `search_code` against
`github/gh-aw-mcpg`
- `list_issues`, `get_file_contents` against `github/gh-aw`

### Out-of-Scope Blocking (expected: blocked)
- All repo-scoped calls against `octocat/Hello-World`

### Global API Blocking (expected: blocked)
- `search_repositories`, `search_users`

### Integrity Filtering (expected: filtered)
- Validates that content from non-approved authors is filtered

## Frontmatter
Uses `repos` and `min-integrity` fields directly on `tools.github` per
the gh-aw compiler format:
```yaml
tools:
  github:
    toolsets: [repos, issues, pull_requests, search]
    repos: ["github/gh-aw*"]
    min-integrity: approved
```

## Related
- PR #1723 (guard flag cleanup)
- PR #1726 (per-server guard mode)
- Issue #1711 (guards tracking)
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