Bug 2503159 (CVE-2026-16456) - CVE-2026-16456 odh-model-controller: odh-model-controller: Cross-namespace secret read via NIM Account CRD confused deputy
Summary: CVE-2026-16456 odh-model-controller: odh-model-controller: Cross-namespace se...
Keywords:
Status: NEW
Alias: CVE-2026-16456
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
high
high
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-07-21 10:13 UTC by OSIDB Bzimport
Modified: 2026-08-10 18:46 UTC (History)
1 user (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-07-21 10:13:01 UTC
The loadSecret function in internal/controller/nim/handlers/api_key_manager.go (line 97, current main) reads the Secret namespace verbatim from user-controlled input:

secretNs := account.Spec.APIKeySecret.Namespace
if secretNs == "" {
    secretNs = account.Namespace
}
apiKeySecretSubject := types.NamespacedName{Name: account.Spec.APIKeySecret.Name, Namespace: secretNs}
if err := a.Client.Get(ctx, apiKeySecretSubject, apiKeySecret); err != nil {

AccountSpec.APIKeySecret (api/nim/v1/account_types.go line 13) is typed corev1.ObjectReference, which carries an arbitrary, caller-supplied Namespace field. The admission webhook (internal/webhook/nim/v1/account_webhook.go) does not validate this field at all: ValidateCreate (lines 63-79) only calls verifySingletonInNamespace (rejecting a second Account in the same namespace), and ValidateUpdate (lines 82-85) is a no-op.

The odh-model-controller ServiceAccount is granted cluster-wide get/list/watch on secrets via its own ClusterRole (config/rbac/role.yaml), so loadSecret's Client.Get() call succeeds regardless of which namespace the attacker names.

Trigger: any authenticated user with permission to create a custom resource in any namespace (e.g. the standard OpenShift edit role) can create:

apiVersion: nim.opendatahub.io/v1
kind: Account
metadata:
  name: attack-account
  namespace: attacker-namespace
spec:
  apiKeySecret:
    name: target-secret
    namespace: victim-namespace

The controller reads target-secret from victim-namespace and, via internal/controller/nim/handlers/pull_secret_handler.go (getApplyConfig, ~lines 104-123), writes the extracted API key value into a new dockerconfigjson Secret (<account-name>-pull) created in the attacker's own namespace (attacker-namespace), completing the confused-deputy chain (CWE-441).

Partial mitigation noted by the reporter: the Secret informer cache is label-scoped to opendatahub.io/managed=true, limiting the attack to RHOAI-managed Secrets, but high-value targets (NGC API keys, data-connection Secrets with cloud credentials) typically carry that label.

Independently confirmed against the current public upstream source (opendatahub-io/odh-model-controller, main branch, checked 2026-07-21): loadSecret, the AccountSpec.APIKeySecret type, and both webhook methods are unchanged from what's described above; the fix has not landed on the public upstream main branch yet (only in private, embargoed backport MRs against the rhoai-2.25/3.3/3.4 release branches).

Reported via Red Hat Product Security's Project Glasswing (Mythos) automated security audit; originating Jira issue: RHOAIENG-69384.

Affected versions: all RHOAI releases with NIM integration enabled (accounts.nim.opendatahub.io CRD present); private fix MRs are open against rhoai-2.25, rhoai-3.3, and rhoai-3.4 as of 2026-07-13.


Note You need to log in before you can comment on or make changes to this bug.