Skip to content

Enforcing one policy across every coding agent you run

A managed agent policy is one set of organization rules (sign-in, models, permissions, MCP servers, plugins, hooks, versions) written once, rendered into each tool’s enforcement file: managed-settings.json for Claude Code and GitHub Copilot, requirements.toml for Codex, and Cursor’s admin settings. Developers cannot override what those files enforce; controls a tool cannot express (listed per tool below) need a compensating check.

Security approved Claude Code in March with an MCP allowlist. Since then the platform team moved to Codex, the frontend team runs Cursor, and Copilot turned on its Claude and Codex agents. Four tools read four config files, and nobody can say which MCP servers a given laptop allows today.

This page is for the CTO who owns agent policy and the tech lead who rolls it out. It is step 11 of the CTO track. It follows agent identity, credentials and secrets, whose deny rules and MCP scopes become managed settings here, and it leads to the agent platform team, which runs this policy as a product.

What one managed policy across vendors gives you

Section titled “What one managed policy across vendors gives you”
  • A ten-control baseline, written as intent rather than as one vendor’s keys.
  • How Claude Code, Codex, Cursor and GitHub Copilot deliver and verify managed policy, and how to govern cloud agents.
  • Policy files checked against Claude Code 2.1.283, Codex 0.157.1 and GitHub’s Copilot docs on 2026-09-26.
  • A policy repository, a CI allowlist parity check, a canary rollout, verification steps and failure modes.

What should the policy say before you touch any config?

Section titled “What should the policy say before you touch any config?”

Write the intent first, in one file your security lead signs. Vendor keys change from release to release; the intent changes only when your risk appetite does. This baseline is moderate: it blocks the ways a laptop agent leaks data or escapes review, and leaves day-to-day permission prompts to teams, as described in permissions and sandboxing for agents.

#ControlBaseline ruleWhy it is org-level, not team-level
1Sign-inCompany account or organization onlyPersonal accounts bypass data terms and audit
2ModelsOnly models your data terms cover; default set centrallyAny other model is a data-handling breach
3Permission floorBypass or “allow all” modes are off on developer machinesOne flag removes every other control
4Secret readsAgents cannot read .env* or secrets/**The threat model needs secrets out of context
5MCP serversOnly approved servers load, matched by URL or exact commandAn unvetted server is code with your tokens
6Plugins and marketplacesOnly the official marketplace and your ownOne plugin bundles hooks, MCP servers and skills
7Command rulesDestructive commands, such as force-push, are forbiddenCheap to enforce, expensive to undo
8Minimum versionClients below the approved version refuse to startSecurity fixes and policy keys arrive in new versions
9TelemetryUsage events go to your collectorAudit and cost reports need one data source
10Cloud agentsAgent PRs pass the same required checks as humansTheir policy lives at the Git host

Controls 3 and 4 are not optional for any tool. Controls 5 and 6 drift most, because each tool configures MCP and plugins its own way. To vet what goes on the MCP list, see MCP registries and gateways.

How does each tool enforce managed policy?

Section titled “How does each tool enforce managed policy?”

Claude Code and GitHub Copilot share most of one managed-settings.json dialect, including permissions.deny, disableBypassPermissionsMode, strictKnownMarketplaces and allowedMcpServers. Codex uses a separate TOML constraints file.

Claude Code (2.1.283)Codex (0.157.1)CursorGitHub Copilot
Policy filemanaged-settings.json (plus managed-settings.d/*.json, managed-mcp.json)requirements.tomlAdmin dashboardmanaged-settings.json in .github-private/copilot/
Deliveryclaude.ai admin console (server-managed), MDM (the com.anthropic.claudecode managed preferences domain on macOS, HKLM\SOFTWARE\Policies\ClaudeCode on Windows, per Anthropic’s managed settings docs), or a system fileWorkspace-delivered layer, macOS MDM, or /etc/codex/requirements.tomlteam and mdm setting sources exist (@cursor/sdk 1.0.32)Server-managed from .github-private, MDM, or a system file (MDM: macOS and Windows only)
File path (Linux)/etc/claude-code/managed-settings.json/etc/codex/requirements.toml—/etc/github-copilot/managed-settings.json
Several sourcesFirst source with a policy key wins by default; managedSourcesBehavior: "merge" composes themSources compose; /debug-config shows which one set each constraint—MDM > server-managed > file > user, per key; permissions.deny/ask/allow and sandbox compose most-restrictively; allowedMcpServers is the intersection
Sign-inforceLoginMethod, forceLoginOrgUUIDallowed_login_methods + allowed_chatgpt_workspacesSSO (confirm)forceLoginOrgs (MDM or system file)
ModelsavailableModels + enforceAvailableModels; deniedModels (v2.1.283, latest channel)model_provider pin; [models.new_thread] sets a defaultReported: Router model allow and block lists; Privacy Mode gates retention-requiring models (confirm)AI Controls: enable, disable or delegate each model; model key sets a default only
Permission floorpermissions.disableBypassPermissionsMode, permissions.disableAutoModeallowed_approval_policies, allowed_sandbox_modesReported: agent permissions per group (confirm)permissions.disableBypassPermissionsMode
Telemetryenv with OTEL_* variablesNot a requirements key; [otel] in a distributed config.toml or MDMNot verifiedtelemetry (CLI, VS Code, JetBrains)
MCP allowlistallowedMcpServers + allowManagedMcpServersOnly[mcp_servers.<name>.identity]Not verifiedallowedMcpServers / deniedMcpServers
Plugins and hooksstrictKnownMarketplaces, blockedMarketplaces; allowManagedHooksOnly[marketplaces] with restrict_to_allowed_sources; allow_managed_hooks_onlyReported: team marketplace with plugins set Default On or Required (confirm)strictKnownMarketplaces, enabledPlugins; hooks are not a managed key
Per-group policySeparate files, or a Claude apps gateway per IdP groupOne workspace layer; separate files per device groupReported: Organizations › Teams › Groups (confirm)Team overrides with overridable and team-mappings.json
Check on a machine/status → Setting sources; claude doctor/debug-config in the TUIAdmin dashboardValidator on the AI Controls Agents tab

Each tab holds one file implementing every control that tool can express, with the gaps listed below it. The MCP allowlist is the same in every tool: GitHub’s and Sentry’s remote servers and Playwright’s local one. Pin the version you vetted; @latest approves whatever ships next. Replace the organization UUID, marketplace repository and collector URL with your own.

Deploy this through the claude.ai admin console, MDM, or the system path: /Library/Application Support/ClaudeCode/ on macOS, /etc/claude-code/ on Linux and WSL, and C:\Program Files\ClaudeCode\ on Windows.

{
"forceLoginMethod": "claudeai",
"forceLoginOrgUUID": ["00000000-0000-0000-0000-000000000000"],
"availableModels": ["opus", "sonnet"],
"enforceAvailableModels": true,
"permissions": {
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"],
"disableBypassPermissionsMode": "disable"
},
"allowedMcpServers": [
{ "serverUrl": "https://api.githubcopilot.com/*" },
{ "serverUrl": "https://mcp.sentry.dev/*" },
{ "serverCommand": ["npx", "@playwright/mcp@0.0.83"] }
],
"allowManagedMcpServersOnly": true,
"strictKnownMarketplaces": [
{ "source": "github", "repo": "anthropics/claude-plugins-official" },
{ "source": "github", "repo": "acme/agent-plugins" }
],
"disableSideloadFlags": true,
"requiredMinimumVersion": "2.1.274",
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.acme.internal:4317"
}
}

What the non-obvious keys do:

  • availableModels alone leaves the Default option on the account’s runtime default; enforceAvailableModels makes Default obey the list. The model key is only an initial selection.
  • allowManagedMcpServersOnly stops users from widening the list. Once one serverUrl entry exists, every remote server must match a URL pattern. A serverName entry is never a security control, because users pick the name.
  • disableSideloadFlags rejects --plugin-dir, --plugin-url, --agents and --mcp-config at startup (v2.1.193 or later), so CI runners that pass --mcp-config need their own policy file.
  • OTEL_EXPORTER_OTLP_PROTOCOL is required: Claude Code has no default OTLP protocol, so without it nothing is exported and control 9 fails silently. Port 4317 is gRPC; use http/protobuf for a 4318 endpoint.
  • requiredMinimumVersion is 2.1.274, the stable channel on 2026-09-26. It blocks older binaries at startup only.
  • For a fixed MCP set users cannot extend, deploy managed-mcp.json instead, through MDM or the system path; the admin console cannot deliver it.

The stricter tier adds allowManagedPermissionRulesOnly and allowManagedHooksOnly. Both switch off project-level rules and hooks that teams use as quality gates, so reserve them for regulated data.

How do you govern cloud agents that only open pull requests?

Section titled “How do you govern cloud agents that only open pull requests?”

Copilot cloud agent, the Claude and Codex agents inside Copilot, Jules and Devin run on remote machines, so nothing deployed to a laptop reaches them. A Claude Code cloud session likewise reads only server-managed settings. Govern these agents where their work lands: the Git host.

ControlWhere it livesApplies to
Which agents may runCopilot policies in AI Controls, per agent; the repositories each vendor’s GitHub app can accessAll of them
What can mergeRulesets or branch protection: required reviews, required status checks, no bypass for bot identitiesEvery agent that opens a PR
Which secrets a run seesEnvironment and repository secrets scoped per workflow; Jules’s GitHub Action reads its key from a repository secret, JULES_API_KEYAgents started from GitHub Actions
What was doneThe enterprise audit log, including agentic events in AI Controls; your CI logsCopilot agents; every agent through CI

Jules’s and Devin’s admin documentation could not be reached on 2026-09-26, so this page makes no claim about their in-product settings. The Git-host boundary works for any vendor, which is why control 10 reads “the same required checks as humans”. The evidence bundle makes a PR mergeable without a human reading every line.

How do you distribute and version one policy across vendors?

Section titled “How do you distribute and version one policy across vendors?”

Treat the policy like any other production configuration: one repository, reviewed changes, CI gates and a staged rollout.

  1. Create the policy repository. One directory per tool, rendered from the intent table.

    agent-policy/
    ├── POLICY.md # the ten-control intent table, signed by security
    ├── CHANGELOG.md # one entry per version: what changed and why
    ├── CODEOWNERS # security + platform team must approve
    ├── claude-code/managed-settings.json
    ├── claude-code/ci/managed-settings.json # CI runners: no disableSideloadFlags
    ├── codex/requirements.toml
    ├── copilot/managed-settings.json # synced to .github-private/copilot/
    ├── cursor/EVIDENCE.md # the dated answers from the Cursor tab
    └── tests/mcp_parity.py
  2. Gate every change in CI. Parse every file, then fail when the MCP allowlists disagree. This script, run against the three files on this page, exits 1 when Claude Code and Copilot differ or when Codex and Claude Code disagree in either direction.

    """Fail CI when the MCP allowlists disagree across tools."""
    import fnmatch
    import json
    import sys
    import tomllib
    claude = json.load(open("claude-code/managed-settings.json"))
    copilot = json.load(open("copilot/managed-settings.json"))
    with open("codex/requirements.toml", "rb") as f:
    codex = tomllib.load(f)
    def allowlist(settings):
    entries = settings.get("allowedMcpServers", [])
    urls = {e["serverUrl"] for e in entries if "serverUrl" in e}
    cmds = {tuple(e["serverCommand"]) for e in entries if "serverCommand" in e}
    return urls, cmds
    problems = []
    if allowlist(claude) != allowlist(copilot):
    problems.append(f"Claude Code and Copilot differ: {allowlist(claude)} vs {allowlist(copilot)}")
    codex_urls, codex_cmds = {}, {}
    for name, req in codex.get("mcp_servers", {}).items():
    ident = req.get("identity", {})
    if isinstance(ident.get("url"), str):
    codex_urls[ident["url"]] = name
    cmd = ident.get("command")
    if isinstance(cmd, dict):
    argv = (cmd["executable"], *(a.get("value", "") for a in cmd.get("args", [])))
    codex_cmds[argv] = name
    claude_urls, claude_cmds = allowlist(claude)
    for url, name in codex_urls.items():
    if not any(fnmatch.fnmatch(url, p) for p in claude_urls):
    problems.append(f"Codex allows {name} at {url}; Claude Code does not")
    for argv, name in codex_cmds.items():
    if argv not in claude_cmds:
    problems.append(f"Codex allows {name} as {' '.join(argv)}; Claude Code does not")
    for pattern in claude_urls:
    if not any(fnmatch.fnmatch(url, pattern) for url in codex_urls):
    problems.append(f"Claude Code allows {pattern}; Codex has no matching server")
    for argv in claude_cmds - codex_cmds.keys():
    problems.append(f"Claude Code allows {' '.join(argv)}; Codex has no matching server")
    print("\n".join(problems) or "MCP allowlists agree across Claude Code, Codex and Copilot")
    sys.exit(1 if problems else 0)

    Run it with python3 tests/mcp_parity.py (Python 3.11 or later, for tomllib) as a required check, next to python3 -m json.tool on each JSON file.

  3. Roll out to a canary group first. Deliver the new version to the platform team’s machines for two working days. Server-managed changes arrive within about an hour in Claude Code and Copilot; MDM is checked every 30 minutes by Claude Code and hourly by Copilot; a file change needs a Copilot restart.

  4. Promote to everyone and pin the version. Tag the release (agent-policy v1.4) and put the version string in a comment at the top of each file, so a support request can name it.

  5. Announce removals first. A new deny rule or a removed MCP server breaks someone’s workflow; post the changelog entry before the rollout and name the replacement.

The platform team owns the repository and the rollout; the security lead approves every change to POLICY.md and signs off the quarterly audit. The operating model has the full RACI.

A config file proves only that someone wrote it. Prove enforcement on real machines, on a schedule.

  1. Read the effective source on a sample of machines. In Claude Code, /status must show Enterprise managed settings with the source you deployed, such as (remote) or (file); Skipped sources means a higher-ranked source won, and claude doctor lists entries dropped as invalid. In Codex, /debug-config shows which source set each constraint. For Copilot, the Agents tab validator must show no issues.
  2. Run negative tests. Add an unlisted MCP server on a canary machine and confirm each tool refuses it. Start Claude Code with --dangerously-skip-permissions, which it must reject, and start Codex with -a never, then confirm that it prints a startup warning and that /debug-config shows on-request.
  3. Watch the audit stream. Claude Code emits claude_code.plugin_installed and claude_code.plugin_loaded events to your OpenTelemetry collector; third-party plugin names are redacted unless you set OTEL_LOG_TOOL_DETAILS=1. Copilot’s enterprise audit log records agentic events. Route both to the pipeline described in agent observability.
  4. Audit quarterly. Compare the repository, the delivered settings and the negative tests; an unexplained difference is a finding with an owner and a date.

Copy-paste prompts for managing agent policy

Section titled “Copy-paste prompts for managing agent policy”

What breaks when you enforce one policy across vendors?

Section titled “What breaks when you enforce one policy across vendors?”

The Claude Code policy file is silently ignored. You deployed the file by MDM, and someone also set one key in the admin console. By default Claude Code uses only the highest-ranked source that delivers a policy key. Recovery: read Skipped sources in /status, then use one source or set managedSourcesBehavior to "merge" (v2.1.242 or later).

A malformed file locks people out. Claude Code refuses to start when a managed file is not valid JSON; Copilot treats a malformed allowlist as an empty one, which blocks every non-built-in MCP server. Recovery: roll back to the previous tag; the CI parse step prevents a repeat.

Codex disables an approved server. Its name differs from the requirement key, or it was registered with npx -y against an identity without -y. Recovery: publish the exact codex mcp add command for each approved server, with the names and argv from requirements.toml (checked against codex mcp add --help, 0.157.1):

Terminal window
codex mcp add github --url https://api.githubcopilot.com/mcp/
codex mcp add sentry --url https://mcp.sentry.dev/mcp
codex mcp add playwright -- npx @playwright/mcp@0.0.83

CI jobs break after the rollout. disableSideloadFlags rejects --mcp-config. codex exec asks for approval policy never; if the list omits it, Codex starts with a startup warning and falls back to the first allowed policy, so a headless run gets approval requests nobody can answer (checked in codex-cli 0.157.1). Recovery: give CI runners their own policy file whose allowed_approval_policies includes never, and keep those runners on the hardened workflows from the previous step.

Copilot agents ignore the MCP allowlist. GitHub marks allowedMcpServers, deniedMcpServers and permissions.deny as unsupported on Copilot cloud agent, and the Claude and Codex agents inside Copilot have their own policies. Recovery: configure cloud agent MCP servers per repository or in enterprise custom agent profiles, and review each agent’s row in AI Controls.

New models appear for everyone. Copilot’s Default availability policy and Claude Code’s availableModels without enforceAvailableModels both let unlisted models through. Recovery: set both deliberately and check the model list in the quarterly audit; the models hub lists what is current.

Long-running sessions lag the rollout. requiredMinimumVersion only blocks new starts. Recovery: announce a restart window, then check versions in telemetry.

Where to go next with managed agent policy

Section titled “Where to go next with managed agent policy”