An enterprise approves an application, then repeats that approval in every provider’s console. CIMD gives those providers a common client identifier and metadata source. It does not give them a common review decision, or a registration that ends when that decision expires.
The proposed CIMD Software Statement drafts let independently operated providers consume the same review as an explicit, renewable condition of admission, while each keeps its own authorization decision. The review travels between providers, stays bound to the metadata that was reviewed, and can govern how long a registration remains valid. The reviewer can be a marketplace, or the enterprise itself where its identity provider isn’t in the path.
Take one hosted application that a customer connects to two providers, with no identity provider in the path to carry the customer’s decision. The customer’s reviewer evaluates the application’s metadata document once and signs a statement for each provider over the same digest, naming that provider and the customer’s tenant there. Each provider requires that reviewer in the tenant and still decides, through its own grant and policy, what the application may access. When the publisher changes the document, both providers refuse new admissions until the reviewer signs the new version, while open grants keep refreshing as long as the new document still carries the application’s key. When the customer withdraws its approval, each provider stops new grants once its status check finds the withdrawal and, under the drafts’ lifecycle extension, stops refreshing grants opened on it. The drafts define each step, and none of it ships today.
In July, Aaron Parecki published a clear account of the missing trust anchor in Dynamic Client Registration and how Client ID Metadata Documents address it. He’s right about the core problem. Open Dynamic Client Registration lets a client walk up and assert an identity without demonstrating any authority over it. CIMD changes the shape of the problem by making the client_id an HTTPS URL, with the document at that URL as the source of the client’s metadata.
That gives an authorization server something outside the client’s request to check. It can fetch the URL, require the document’s client_id to match exactly, apply policy to the origin, and lean on ordinary Web security to establish who controls the metadata location. Knowing who controls the metadata still leaves the server to decide what that control is worth.
What I want to add to Aaron’s analysis is a distinction between four questions that are easy to blur: control, review, runtime proof, and authorization.
| Question | Mechanism | What it establishes | What it does not establish |
|---|---|---|---|
| Who controls this client identifier and its metadata source? | CIMD | control of the HTTPS URL and an authentic source for the metadata currently served there | that the software is benign, reviewed, or the binary now running |
| Who reviewed and vouches for this metadata? | a trusted issuer, via a statement under the proposed CIMD Software Statement profile | a named issuer’s signed decision about the exact bytes of one version of the document, for any audiences it names | that every claim is independently true or that access should be granted |
| What software instance is presenting now? | client authentication or instance attestation | proof of a credential when the client authenticates, instance identity through per-instance provisioning or attestation, and platform provenance only through attestation | publisher reputation or approval of the client as a whole |
| What may this client do? | the OAuth grant and local authorization policy | the access a resource owner or policy actually granted, at this server | that the software was reviewed, or that the binary is genuine |
The first two are establishment anchors, and the last two are runtime questions, answered when a presenter shows up and when access is decided. Blur them and CIMD starts to sound like a code review, or a statement like an access grant. A fifth question, whether a reviewed client may operate in a particular customer’s tenant, belongs to the customer, and the enterprise section below takes it up.
The Gap CIMD Actually Closes
RFC 7591 Dynamic Client Registration lets a client send metadata to an authorization server and get back a local client_id. Unless the server requires an initial access token, a software statement, or some other policy, it’s evaluating values supplied in the same request by the party asking to be registered. A malicious client can claim another product’s name, logo, software_id, or support URL, and RFC 7591 can only tell the server to treat those values as self-asserted and apply its own checks.
With CIMD, the client brings its own identifier instead: a URL it controls, like https://client.example/oauth/client.json. The server retrieves it and expects a document like this:
| |
In this example, the client_id, the metadata source, the redirect URI, and the key location all sit under one domain. A server can pre-enroll the URL, tier domains, or add friction for an unfamiliar origin. The same client presents the same identifier everywhere, and each server decides what that identifier is allowed to do.
But HTTPS proves control of an origin, nothing more. acme-login.example can authentically publish metadata from acme-login.example. A compromised publisher domain can authentically publish malicious metadata. A desktop binary can truthfully point at its publisher’s URL without being the publisher’s binary.
So the claim CIMD supports is:
This metadata came from the party controlling this client identifier URL.
That’s much stronger than an ungrounded POST body. It’s not the same as “this software is trustworthy.”
The Second Anchor Carries a Review Decision
Aaron notes that RFC 7591 already defines software_statement, while “the DCR spec itself says nothing about how to create one, what it should contain, or how keys should be managed.” The RFC tells a registration endpoint how to consume a statement, a signed JWT of claims about client software whose trusted claims take precedence over plain JSON, and leaves to deployments the two questions that matter most: how a client gets one, and why a server should trust whoever signed it.
The CIMD Software Statement drafts are my attempt at both questions, a family of individual drafts built on CIMD with the statement itself at the core. The statement carries no client metadata. It records a review of the exact bytes of the client’s own document, which stays the metadata. That choice and the digest belong to this profile, since in RFC 7591 a statement’s claims are client metadata that take precedence over the request. The statement and admission at request time are the core. Registration, lifecycle, issuance, and attested keys are extensions a deployment adds as it needs them.
The second anchor isn’t the JWT by itself. It’s the consumer’s scoped trust in the statement issuer, and the statement carries that issuer’s decision, ideally made by review. The statement makes that decision portable, but each server has to already understand and accept the issuer’s review process, since nothing machine-readable travels about assurance or review policy. Domain control grounds in WebPKI and DNS, which the whole web already shares, while issuer trust grounds in pairwise configuration and so needs explicit scoping. A second anchor isn’t necessarily a second independent authority. A publisher’s own program, a marketplace, and an enterprise reviewer use the same format and give very different assurance, and the example below comes from a publisher program.
OpenID Federation already provides most of this for clients that are Federation Entities. Its trust marks are signed third-party vouching, backed by trust chains, metadata policy, key discovery, and a status endpoint for withdrawal, and a relying party can also accept marks from an issuer it accredits out of band. Its Automatic Registration admits a client at request time with no prior registration step, and its Explicit Registration provides one where a deployment wants it. Both apply to plain OAuth clients as well as OpenID Connect, and neither registration may outlive the trust chain it was made from. Trust marks allow additional claims, so an ecosystem could define one that carries a document digest.
The difference is what Federation requires and what it leaves undefined. A Federation client publishes a signed Entity Configuration and chains to a trust anchor each server accepts, while a CIMD client publishes one unsigned, mutable document at a URL it controls. A trust mark attests the entity, and Federation defines no binding between a mark and a version of the entity’s metadata, so a digest claim would still need rules for what a server does with it: admission, a mismatch, presenter binding, and how long a registration lasts. This profile specifies those rules. Federation already has third-party vouching. What this profile adds is a review decision bound to the exact bytes of a CIMD document, carried in OAuth’s existing software_statement parameter, with the admission and validity rules a server needs to consume it. Issuer trust is configured at each server, and a federation can sit above that when pairwise configuration stops scaling.
The statement’s sub is the exact CIMD URL. Its cimd_digest is the SHA-256 of the document bytes the issuer evaluated. Its optional aud restricts which authorization servers may accept it, and statement_uses says whether the review may govern registration, presentation (admission at request time), or both. A consuming server takes the client’s metadata from the document the digest names and ignores any metadata claim a statement carries.
A shortened statement from a publisher program, naming two providers, has claims like these. A tenant-scoped statement like the opening’s names exactly one. On the wire it’s a signed JWS typed software-statement+jwt, and the header’s kid points into the issuer’s dedicated statement key set, published at software_statement_jwks_uri when the issuer is an authorization server and otherwise recorded in the consumer’s trust configuration:
| |
The two anchors compose:
- The client publishes its canonical metadata at a URL it controls.
- The statement issuer retrieves that URL, verifies the CIMD rules, snapshots the exact content, performs whatever review its policy requires, and signs the URL and digest, usually naming an audience.
- The client presents the statement in a token request or a pushed authorization request, carries it in an RFC 7591 registration, or publishes it at the
software_statements_uriits document names so that servers can pull it. - The consuming authorization server verifies the statement under a configured issuer policy, fetches the document, compares the digest, binds the presentation to the reviewed document, and admits the client on that document’s metadata, with or without a registration.
The experimental issuance draft covers how a client obtains a statement, through a browser flow that returns a short-lived code or through token exchange, with Deferred Token Response carrying a review that takes hours or days. An issuance request can’t ask for access, and no protected resource may accept a statement as an access token. Consumers need only the core to verify statements, so an issuer can just as well sign from an approval service, a marketplace submission, or a publisher’s build pipeline.
The issuer isn’t replacing the domain anchor. It’s signing a decision about the domain-anchored identity. A copied statement admits nothing by itself. Where the reviewed document permits only key-based client authentication, a thief needs the publisher’s key as well as its statement. A statement over a document that also permits none can be presented by anyone with a copy under the weaker public-client binding described below. At registration a statement is a bearer artifact, but a copier who registers gets only the publisher’s reviewed metadata, since the registration draft takes none from the request, and can’t authenticate where the registered method needs a document key, though it can occupy the registration at that server. Without an audience, any server that trusts the issuer accepts such a copy, which is why the core recommends naming one.
Admission at request time keeps CIMD’s property of resolving a client with no prior registration. The statement can’t sit inside the document, as CIMD otherwise permits, because it can’t live inside the bytes it hashes. The software_statements_uri pointer can, and the digest covers it.
Without the digest, sub points at a mutable URL and nobody can say whether today’s document is the one reviewed yesterday. With it, a mismatch means the document changed after review, and the server either rejects the request or treats the client as unreviewed. At registration a mismatch is fatal. Hashing exact served bytes avoids any argument over JSON canonicalization, but the served representation becomes canonical, down to byte order marks and trailing newlines, and unstable bytes break the digest for honest reasons. The cost lands on publishers, mostly as an admission gap. To stay admitted as reviewed software, a client needs a new statement for every change, from a spelling fix to a new redirect URI, and under the current issuance flow a changed document is served before a statement over it can exist. While a review that takes hours or days is pending, a provider that requires reviewed software refuses new admissions, though grants already open keep refreshing, provided a confidential client’s new document still carries the key each grant proved. The gap comes from reviewing a document only once it’s served, not from the digest. A staged review, in which the issuer signs the next document before it’s published, would close it without changing what the digest means, as an extension to issuance. The issuer decides how much review each change gets, and a statement vouching for particular members is left to an extension.
The digest covers the document, not content fetched from URLs it names. Changing an inline key changes the reviewed bytes, but rotating keys behind an unchanged jwks_uri doesn’t, and neither does a new image behind logo_uri. An issuer that needs the keys reviewed vouches only for documents that carry them inline, and accepts a new statement at every rotation. More generally, the statement covers the reviewed bytes, so anything else the issuer checked, such as the key set or who controls the redirect hosts, can change while the statement stays valid.
Dick Hardt’s proposed OpenID Federation Well-Known Binding uses the same exact-octet digest for a different purpose. An Entity signs digests of its own well-known documents and their key sets in its Entity Configuration, so a verifier that validates its Trust Chain can authenticate them without relying on the Web PKI alone. That shows the Entity authorized its current documents and keys, not that a reviewer reviewed them, so it strengthens the first of the four questions and leaves the second to a statement. It could root a statement issuer’s key set in a Trust Chain, and with a CIMD profile it could bind a client’s jwks_uri keys without carrying them inline. A rotated key set signed that way carries the client’s authority, not the reviewer’s, and the profile would have to keep federation metadata from overriding reviewed metadata. The Federation sketch works through both.
Bring Your Own App Approvals
If you administer Google Workspace, there’s a console where you control which third-party and internal apps can access your organization’s data: app by app, identified by OAuth client ID, marked trusted, limited, or blocked. That works for the one provider. Every other provider the enterprise adopts brings another console and another allowlist, and the lists drift apart as each is edited separately.
A provider’s marketplace decides which software may exist as a client on its platform, and a customer decides which of that software may operate in its tenant. The drafts can carry either decision, one statement per admission. Where a tenant requires the customer’s reviewer, admission there rests on that reviewer’s statement, and the marketplace’s decision becomes the provider’s own local policy.
Where the customer’s identity provider mediates the grant, that second decision already has an enforcement point. Under an ID-JAG, or MCP’s Enterprise-Managed Authorization, the identity provider issues assertions only for applications the customer permits, and each grant needs one. Stopping issuance prevents new assertions. An assertion already issued stays usable until it expires, subject to the receiving server’s policy, since ID-JAG lets a client resubmit it in place of a refresh token. Where the identity provider is in the path, the customer needs no statement.
Where it isn’t, as when one vendor’s service connects to another’s with no user present, or where the customer wants an auditable record that travels, the customer runs a reviewer of its own. Its statements name the provider’s tenant in aud_tenant, a claim borrowed from ID-JAG, and a tenant-scoped statement names exactly one authorization server, so one review becomes a statement per provider. Each provider configures the reviewer as required for the covered client namespaces in the tenant, so a new admission of that software there needs the customer’s current statement, whatever the marketplace listing says. A reviewer serving several customers signs for each under its own iss.
The requirement admits the software, not one of its customers. That the software was reviewed, that the customer approved it in its tenant, and that this transaction acts for that customer are three separate facts, and a statement carries only the first two. Vendor software that serves many customers with one statement and key can be pointed at this tenant by any of them, so a deployment that needs the third fact records it at the provider. The requirement also ends no grant admitted before it was configured, covers no registration made without a statement, and can’t keep out software that a user controlling the browser chooses to run.
A tenant configures a customer’s reviewer much as it configures an IdP today, and the enterprise IdP’s authorization server is a natural candidate to run it. ID-JAG carries the IdP’s per-grant word about a user and a client together. A software statement would carry its standing word about the software, signed with keys the IdP uses for nothing else. Same issuer infrastructure, distinct authority and payload. The relationship exists, but the authority is new, and a provider still decides explicitly to accept the IdP’s word for app approvals. Cheaper, because the wiring is there, but still not free.
The customer’s gain is narrower, and some of the work moves rather than disappears:
| Model | Enterprise maintains | Provider maintains |
|---|---|---|
| Provider allowlists | An approval at each provider | Its own policy and administration |
| Automation through admin APIs | One approval record and an integration per provider | Administrative APIs usable for that, possibly with expiring approvals it builds |
| Software statements | One review, and a statement per provider | Trust in each customer’s reviewer, and the common admission and lifecycle rules |
What the statements add is an issuer-authenticated decision bound to reviewed metadata, with validity and renewal rules that every adopting provider enforces the same way.
I’ve filed a lightweight version of this against ID-JAG itself. Issue #121 proposes an optional cimd_digest in the assertion the IdP signs anyway, so a provider can verify a client’s metadata and provision it just in time. The statement is the standing version, with its own lifecycle and audiences, and the assertion-carried digest is the minimal per-grant one. A provider can use both.
Agents raise the same question. A draft MCP extension, Reviewed Client Software, applies these drafts to MCP, which recommends identifying clients by CIMD. Its authorization server pulls statements from the client’s software_statements_uri and leaves the tenant decision to Enterprise-Managed Authorization. The agent case is a post of its own.
When a Review Ends
What a provider does when a review ends depends on which kind of reliance it implements. Under the core draft, a server checks a statement only when a grant opens. Under the registration draft, a current statement is a continuing condition of a registration. The lifecycle draft adds, for either kind, withdrawal through a Token Status List that each server resolves on its own schedule, with an optional Shared Signals event that prompts an earlier check, as well as supersession and moving a grant to a later statement at refresh. Withdrawing a review affects a grant or registration only where the server has made continued reliance on that review one of its conditions.
| What happens | Admission without registration | Registration made from a statement |
|---|---|---|
| The statement expires | New admissions stop. Under the core alone, open grants keep refreshing. The lifecycle draft recommends requiring a later statement at refresh, and requires one where the tenant names a required issuer | The registration stops being valid, and every authorization and token request under it fails, refresh included, until the client delivers a later statement |
| The issuer withdraws it, under the lifecycle draft | New admissions stop, and grants recorded against it can’t refresh until a later statement replaces it | The registration stops being valid until a later statement restores it |
| A narrower statement replaces it | Under the core alone, the older one stays acceptable until it expires. Under the lifecycle draft, a server that has accepted the newer one refuses the older one for new grants, and open grants continue until the client moves them or the issuer withdraws the older one, as the draft asks | The narrowing applies when the client delivers the newer statement, which re-derives the metadata, or when the older one stops being current |
Access tokens already issued run out on their own lifetimes, and a withdrawal reaches each server only when that server’s status check finds it. The registration column is that draft’s contribution, since RFC 7591 ties no registration’s validity to the statement behind it.
Both providers in the opening example require the reviewer. A provider that also admits CIMD clients it hasn’t reviewed admits the client as unreviewed while a changed document awaits review, and again for new grants after a withdrawal. It still refuses to refresh grants opened on a withdrawn statement until a later statement replaces it, since the lifecycle draft never lets a reviewed grant continue under the policy for unreviewed clients.
When You Need the Second Anchor, and When You Don’t
Software-statement issuance has a real cost, so it should be used only where portability or independent review pays for it, and other needs have better fits:
| Need | Fits |
|---|---|
| A single server’s local onboarding decision | CIMD pre-enrollment, an initial access token, or approval-based registration |
| A resource owner’s approval | The ordinary OAuth grant, with CIMD supplying identity |
| A tenant decision where the customer’s identity provider mediates the grant | The assertion that provider already issues |
| Which instance is running | Per-instance credentials or attestation, not a statement |
| A native client whose redirect URI another app on the device could claim | The experimental attested-keys extension on a managed device, since a statement alone admits nothing |
| A client with no hostable metadata identity | Not this profile, which needs a document to digest |
| Issuer trust at multilateral scale | A federation above pairwise configuration |
| A reviewer’s decision reaching servers it doesn’t operate, registrations that end with the review, or an enterprise reaching providers its identity provider can’t | A CIMD software statement |
Where no portable review decision is needed, this statement profile adds little.
What the Statement Actually Says
A valid statement doesn’t make the client’s claims true. The client wrote the source document, and the issuer may have done anything from an automated syntax check to a full security and publisher review.
What a statement actually means is narrower:
At the recorded time, this issuer evaluated this exact document under its policy and chose to vouch for it to the audiences it names, or to any server that trusts it for this client when it names none.
That creates accountability bounded by the issuer’s process, and a server can conclude only what the issuer decided. Interpretable review semantics would need issuer policy metadata that doesn’t exist yet, and the statement doesn’t identify who approved.
And some things you can’t conclude from the statement alone:
- the publisher is the well-known organization a similar name suggests;
- the software is free of vulnerabilities or malicious behavior;
- the binary now running corresponds to the reviewed software;
- the presenter is an authorized instance of that software; or
- any user has granted the client access to a resource.
This is why “attestation” should mean an attributable assertion, not truth by signature.
For routine issuer key rotation, the consumer keeps the old verification keys configured until the statements they signed expire. Recovery from a compromised issuer key stays with the deployment. Removing the issuer from trust stops new admissions on its statements but ends no grant already open, so the server ends those by its own means, for example with refusal records under the lifecycle draft.
Aaron’s Three Hard Cases, Revisited
Aaron identifies three places where domain control isn’t enough. Portable review helps with each of them, though it solves none outright.
Lookalike Domains
CIMD can’t help here, because a lookalike’s metadata is authentic too, authentic to the lookalike domain. A software statement adds a review point where publisher identity, branding, redirect domains, and other evidence can be checked before anyone vouches.
The structural control is namespace-scoped issuer trust, the same discipline I’ve argued for issuer trust generally. A consuming authorization server shouldn’t just record “trust issuer X.” It should record which client identifier namespaces that issuer may attest. A publisher program authorized only for URLs under client.example can’t mint an acceptable statement for https://acme-login.example/, even with a valid signature. Three issuer shapes fall out: a publisher program speaks for its own namespace; an ecosystem reviewer spans enrolled namespaces under its policy; an enterprise issuer spans namespaces inside its tenant.
That limits issuer blast radius. It doesn’t detect every lookalike, and an issuer trusted broadly can still make a bad call. What you get is an accountable policy authority whose claims can be constrained and whose trust can be removed, not a cryptographic check of who owns a name.
Weak Authorization-Server Posture
If a server accepts every CIMD URL without policy, it has gained a stable identifier and better lifecycle mechanics, but not much protection from impersonating metadata. Requiring a statement raises the floor only if statement trust is itself constrained.
Signing keys come from the statement key set of the issuer the server configured, never from a key location inside the statement. The issuer has to be authorized for the sub’s namespace, the lookalike rule above, and for any audience or tenant it names. The core draft carries the full checklist, down to the lifetime cap each server sets for an issuer.
The signature authenticates the issuer. Policy decides the issuer’s authority.
The Cost of Deciding
With CIMD alone, each of M authorization servers can fetch the same document and make an independent approval decision. Among servers that already accept the reviewer’s authority and process, a portable statement changes the cost from M reviews to one issuance review plus M local acceptance checks. A tenant-scoped decision also needs a statement per provider, so issuance repeats while review doesn’t.
The consuming servers still decide which issuers to trust, for which namespaces, metadata, lifetimes, and audiences. What becomes reusable is the expensive evidence-gathering and review, not each server’s final admission decision. Admission at request time also removes the M registrations, and where the server pulls statements from the client’s software_statements_uri, a confidential client sends nothing beyond its ordinary request.
The decision is reusable, and how far the artifact travels is the issuer’s choice. A statement restricted to named audiences needs a new one before another server can accept it, while a statement with no audience restriction can be reused by any server whose trust configuration covers the issuer and the client.
The recurring role already exists. The UK Open Banking Directory and Australia’s Consumer Data Right Register both issue central software statements consumed through RFC 7591-shaped registration, which is portable review in production, defined separately by each ecosystem. Their statements carry metadata inline and no digest, so these drafts as written would not accept them, but the role is the same. The drafts offer one common form of the acquisition, renewal, validity, status, and error handling that each ecosystem defines for itself.
The Runtime Question Remains
The two establishment anchors describe a logical client and a review of its metadata. Neither proves which executable is making a particular request.
The core draft narrows the gap without closing it. A confidential client proves a key the reviewed document carries. Software distributed to end users can’t, so its admission rests on reviewed redirect URIs, PKCE, and a key each installation binds with DPoP, which shows where codes go but not which software holds the grant, and fails against a party that controls the user agent. A native client whose redirect URI another app on the device could claim, such as a loopback address or a private-use scheme, can still protect the flow with PKCE and DPoP, but nothing ties the presenter to the reviewed software, so its statement is review-only. The server can record the review but admits nothing on it, and the registration draft won’t register the client.
OAuth Attestation-Based Client Authentication addresses the runtime layer by letting an instance present key-bound attestation. Ordinary client authentication, private_key_jwt or mutual TLS, identifies the credential holder. It identifies an instance only when credentials are provisioned per instance, which is where client instance identification or workload identity comes in. Under the core draft, though, an instance key counts only if the reviewed document carries it, and any other key needs the attested-keys extension.
The experimental attested-keys draft is the family’s answer for the loopback case. An attester the server trusts independently, typically an enterprise agent on a managed device, attests that an installation’s key belongs to the reviewed software, and the installation proves that key with attestation-based client authentication throughout the grant. On an unmanaged desktop an attester typically can’t show that the software, and not just the device or the user, controls the key, so the client stays review-only.
Where This Came From
The design began in the OAuth working-group discussion of Philippe Dellaert’s Approval-Based Dynamic Client Registration, which surfaced three of its choices: CIMD as the canonical input an approver sees, cimd_digest to bind a durable decision to what was reviewed, and Deferred Token Response for review that outlasts a request. The drafts also started by carrying the marketplace’s decision and the customer’s in one artifact, and split them.
CIMD says where the client identity speaks from. A software statement says who is willing to stand behind a reviewed version of what it said. Runtime attestation says what is speaking now. OAuth still decides what it may do.
Status and Feedback
The CIMD Software Statement drafts are early individual drafts. The statement, registration, and lifecycle drafts were submitted to the IETF as individual Internet-Drafts on November 2, 2026. The issuance and attested-keys drafts are experimental extensions that carry the design end to end. They aren’t submitted yet, and I want feedback on them as much as on the three that are. The questions I most want pressure on:
- how a publisher should ship a metadata change without a gap in new admissions, given that any change voids the review, and whether that calls for staged review, in which the issuer reviews the next document before it’s served, or for partial review in the core;
- whether issuer scoping, by namespace for publisher programs and by tenant for customers, is manageable in practice, and whether consumers need to know what a reviewer is authorized to assert, not only which clients it covers;
- whether the issuance extension’s two acquisition paths, and the attested-keys extension’s managed-device binding for loopback clients, are the right way to finish the flow; and
- whether providers would actually ship registrations that expire with the review behind them, since the provider case depends on it, and whether that continuing-validity contract belongs in an extension rather than the core.
Comments on the OAuth working-group list are especially welcome, and comments on the experimental extensions can also go to the repository’s issues.