CimdPolicy
in package
implements
JsonSerializable
Whether, and on what terms, a `client_id` that is a URL is resolved by fetching the document it names (T21.5, `draft-ietf-oauth-client-id-metadata-document`). # Why this is one nested policy rather than nine fields Every field here is a term of a single decision — *do we fetch a stranger's URL and make a client out of what comes back* — and none of them means anything without [`Self::enabled`]. A tenant that states a CIMD posture states all of it; a tenant that states none inherits its organization's whole posture rather than half of one, which is the only merge that cannot produce a combination neither party wrote. # The two fields that can widen, and the seven that cannot [`Self::enabled`] and [`Self::allow_http`] are **ordered**: a tenant may turn either off but never on, exactly as `dynamic_registration` may only move down its ladder. Everything else names *this tenant's* domains or *this tenant's* bounds, and there is no sense in which one tenant's list of trusted publishers is stricter than another's — the same argument [`OidcPolicy`] already makes for `dcr_allowed_redirect_hosts`. # Every bound here is a security control [`Self::max_metadata_bytes`], [`Self::min_cache_secs`] and [`Self::max_cache_secs`] are not tuning knobs. They are, respectively, the ceiling on a read from an attacker-chosen URL, the floor under how often that read may be repeated, and the ceiling on how long its result may be trusted. Each is clamped again in code against the three constants above, so a settings row written by hand cannot lift them.
Every property is optional: one you leave unset is omitted from the wire request entirely rather than sent as null (§27.4 rule 5).
Table of Contents
Interfaces
- JsonSerializable
Properties
- $allowHttp : bool|null
- $confidentialOnly : bool|null
- $enabled : bool|null
- $maxCacheSecs : int|null
- $maxMetadataBytes : int|null
- $minCacheSecs : int|null
- $restrictSameDomain : bool|null
- $trustedClientIdDomains : array<string|int, mixed>|null
- $trustedRedirectDomains : array<string|int, mixed>|null
Methods
- __construct() : mixed
- Constructs a CimdPolicy.
- fromArray() : self
- Rebuilds a CimdPolicy from one decoded JSON object.
- jsonSerialize() : array<string, mixed>
- Renders this object for `json_encode()`.
- toArray() : array<string, mixed>
- Renders this object back to its wire form.
Properties
$allowHttp read-only
public
bool|null
$allowHttp
= null
$confidentialOnly read-only
public
bool|null
$confidentialOnly
= null
$enabled read-only
public
bool|null
$enabled
= null
$maxCacheSecs read-only
public
int|null
$maxCacheSecs
= null
$maxMetadataBytes read-only
public
int|null
$maxMetadataBytes
= null
$minCacheSecs read-only
public
int|null
$minCacheSecs
= null
$restrictSameDomain read-only
public
bool|null
$restrictSameDomain
= null
$trustedClientIdDomains read-only
public
array<string|int, mixed>|null
$trustedClientIdDomains
= null
$trustedRedirectDomains read-only
public
array<string|int, mixed>|null
$trustedRedirectDomains
= null
Methods
__construct()
Constructs a CimdPolicy.
public
__construct([bool|null $allowHttp = null ][, bool|null $confidentialOnly = null ][, bool|null $enabled = null ][, int|null $maxCacheSecs = null ][, int|null $maxMetadataBytes = null ][, int|null $minCacheSecs = null ][, bool|null $restrictSameDomain = null ][, array<int, string>|null $trustedClientIdDomains = null ][, array<int, string>|null $trustedRedirectDomains = null ]) : mixed
Parameters
- $allowHttp : bool|null = null
-
Permit an
http://client_idand anhttp://fetch. Development only, and it does more than its name says.* AXIAM's shared SSRF guard couples the scheme rule to the address rule — the same seam that lets an integration test point a fetch at a loopback mock server — so a tenant that allowshttpalso allows the first hop to resolve to a private address. Redirect hops are validated strictly whatever this says, and a public deployment that sets it has removed the control that makes169.254.169.254unreachable. (optional) - $confidentialOnly : bool|null = null
-
Refuse a document whose
token_endpoint_auth_methodisnone. Off by default, becausenoneis what every MCP desktop client is. A tenant that turns it on accepts onlyprivate_key_jwtdocuments, which is the posture for a deployment whose CIMD clients are servers rather than desktops. (optional) - $enabled : bool|null = null
-
Off unless somebody turns it on (I1). With this
false, a URL-shapedclient_idis exactly today's unknown client: nothing is fetched, nothing is materialised, and the ordinary repository lookup answers as it always has. (optional) - $maxCacheSecs : int|null = null
-
The ceiling on a document's cache lifetime, in seconds. Clamped to [
CIMD_MAX_CACHE_CEILING_SECS]. (optional) - $maxMetadataBytes : int|null = null
-
The hard cap on how many bytes of a document are read, before it is parsed. Clamped to [
CIMD_MAX_METADATA_BYTES_CEILING]. (optional) - $minCacheSecs : int|null = null
-
The floor under a document's cache lifetime, in seconds. Clamped to [
CIMD_MIN_CACHE_FLOOR_SECS]. (optional) - $restrictSameDomain : bool|null = null
-
Require every
redirect_urishost in the document to equal the host of theclient_idURL itself. On by default, because the document says who the client is and a redirect to somewhere else is the one thing a stolen or mirrored document would want to change. It is turned off for the desktop MCP clients, whose callbacks are on loopback and therefore can never share a host with ahttps://client_id;docs/admin/client-id-metadata-documents.mdsays so and says why. (optional) - $trustedClientIdDomains : array<int, string>|null = null
-
The hosts whose documents this tenant will fetch at all, as globs (
mcp.example.com, or*.example.comfor every host under one domain). An empty list resolves nothing, and enabling CIMD while it is empty is refused — see [validate_cimd_policy]. That is a deliberate departure from "a URL is a client identifier, so any URL will do": the fetch is triggered by an unauthenticated request naming the URL, so an unrestricted list is a request-forgery primitive offered to strangers, bounded only by the SSRF guard's address rules. Naming the publishers a tenant actually fronts costs one settings field and removes the class.*is refused here, and so is a wildcard over a whole top-level domain (*.com): both are the posture the empty list is refused for, spelled differently, and a control with no second control behind it cannot have a one-character bypass and still be the control. It is a floor and not a public-suffix check —*.github.iopasses, and trusting shared hosting stays the operator's decision, bounded by the per-tenant quota rather than by this field.*remains valid in [CimdPolicy::trusted_redirect_domains], whose entries are not fetch targets. (optional) - $trustedRedirectDomains : array<int, string>|null = null
-
The hosts a document's
redirect_urismay point at, as globs. The loopback hosts (127.0.0.1,[::1],localhost) are always allowed, because RFC 8252 §7.3 is how every desktop MCP client receives its callback — so an empty list is not a refusal of everything, it is "loopback only", which is exactly the Claude Code and VS Code profile. (optional)
fromArray()
Rebuilds a CimdPolicy from one decoded JSON object.
public
static fromArray(array<string, mixed> $data) : self
Parameters
- $data : array<string, mixed>
-
The raw wire object.
Return values
selfjsonSerialize()
Renders this object for `json_encode()`.
public
jsonSerialize() : array<string, mixed>
Any Sensitive it carries stays WRAPPED here, so a log line or a
json_encode($model) in application code prints [SENSITIVE]. The one place a secret
is revealed is ManagementTransport, on the way to the wire
and nowhere else (§27.5).
Return values
array<string, mixed>toArray()
Renders this object back to its wire form.
public
toArray() : array<string, mixed>
§27.4 rule 5: a null property is OMITTED, not emitted as null. On a sparse update those two say opposite things — "leave this alone" versus "set this to nothing" — and only omission means the first.