Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff26612
Add BaselineClientScopes to embedded auth CRD
jhrozek May 8, 2026
733744e
Plumb BaselineClientScopes into auth server RunConfig
jhrozek May 8, 2026
03c8229
Validate baseline scopes are subset of supported
jhrozek May 8, 2026
947f64f
Resolve baseline scopes into runtime Config
jhrozek May 8, 2026
f937f70
Forward baseline scopes to AuthorizationServerConfig
jhrozek May 8, 2026
2ebd751
Add UnionScopes helper for DCR baseline injection
jhrozek May 8, 2026
e0d992a
Apply baseline scope union to DCR registration
jhrozek May 8, 2026
b2352fe
Test DCR handler baseline scope behavior
jhrozek May 8, 2026
e216410
Test RunConfig.Validate baseline subset check
jhrozek May 8, 2026
be8f22a
Add end-to-end DCR scope-narrowing regression test
jhrozek May 8, 2026
a78855c
Regenerate swagger docs for BaselineClientScopes
jhrozek May 11, 2026
e6a2fa9
Document baseline scope privilege risk; log at startup
jhrozek May 11, 2026
afa4889
Demote baseline-scope expansion log to Debug
jhrozek May 11, 2026
2fd093b
Clone caller-supplied scope and audience slices
jhrozek May 11, 2026
f643b63
Test direct-caller baseline subset gate
jhrozek May 11, 2026
34575bb
Assert invalid_scope in the 400 branch of negative test
jhrozek May 11, 2026
772995d
Reject empty and malformed baseline scope entries at admission
jhrozek May 11, 2026
4c01d5b
Drop call-site list from ValidateScopeSubset doc
jhrozek May 11, 2026
f576ea9
Clarify scope-response comment in DCR handler
jhrozek May 11, 2026
fd0235a
Add baseline subset check to Config.Validate
jhrozek May 11, 2026
84a4eec
Honor scopesSupported defaults in baseline subset check
jhrozek May 12, 2026
29a3621
Regenerate swagger docs after baseline-validation fix
jhrozek May 12, 2026
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
25 changes: 20 additions & 5 deletions cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,26 @@ type EmbeddedAuthServerConfig struct {
// +optional
Storage *AuthServerStorageConfig `json:"storage,omitempty"`

// AllowedAudiences is the list of valid resource URIs that tokens can be issued for.
// For an embedded auth server, this can be determined by the servers (MCP or vMCP) it serves.

// ScopesSupported is the list of OAuth 2.0 scopes that this authorization server supports.
// For an embedded auth server, this can be derived from the server's (MCP or vMCP) OIDC configuration.
// BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
// included in every client registration. The embedded auth server unions these
// scopes into the registered set returned by RFC 7591 Dynamic Client
// Registration, so a client that narrows the `scope` field at /oauth/register
// can still request the baseline scopes at /oauth/authorize. All values must
// be present in the upstream-derived scopesSupported set; the auth server
// fails to start if any value is missing.
//
// Security: every client registered via /oauth/register will gain the
// ability to request these scopes at /oauth/authorize, regardless of what
// the client itself requested. Keep the baseline narrow (typically
// "openid" and "offline_access"). Adding a privileged scope here — e.g.
// "admin:read" — would grant it to every DCR-registered client, including
// public clients like Claude Code, Cursor, and VS Code.
// +kubebuilder:validation:MaxItems=10
Comment thread
jhrozek marked this conversation as resolved.
// +kubebuilder:validation:items:MinLength=1
// +kubebuilder:validation:items:Pattern=`^[\x21\x23-\x5B\x5D-\x7E]+$`
// +listType=atomic
// +optional
BaselineClientScopes []string `json:"baselineClientScopes,omitempty"`
Comment thread
jhrozek marked this conversation as resolved.
}

// TokenLifespanConfig holds configuration for token lifetimes.
Expand Down
5 changes: 5 additions & 0 deletions cmd/thv-operator/api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cmd/thv-operator/pkg/controllerutil/authserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ func BuildAuthServerRunConfig(
AuthorizationEndpointBaseURL: authConfig.AuthorizationEndpointBaseURL,
AllowedAudiences: allowedAudiences,
ScopesSupported: scopesSupported,
BaselineClientScopes: authConfig.BaselineClientScopes,
Comment thread
jhrozek marked this conversation as resolved.
}

// Build signing key configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -1385,6 +1408,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -2723,6 +2746,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -1388,6 +1411,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -2726,6 +2749,29 @@ spec:
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.

Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
1 change: 1 addition & 0 deletions docs/operator/crd-api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading