It is like handing a janitor the master key ring because the intercom panel trusts whoever fills in the name field
CVE-2026-105312 is a privilege-escalation flaw in the StreamsHub Console operator for Apache Kafka on Kubernetes (Strimzi ecosystem). A Kubernetes tenant who has been granted create permission on Console custom resources (consoles.console.streamshub.github.com) can set spec.containers.api.spec.image to an arbitrary container image. The operator blindly deploys that image into a pod whose ServiceAccount is bound via a ClusterRoleBinding to a ClusterRole granting cluster-wide get/list/watch on all Strimzi CRDs (including KafkaUsers, which hold SCRAM-SHA-512 credentials), patch on Kafkas and KafkaRebalances, and the cluster-monitoring-view role. Affected versions: StreamsHub Console >=0.2.0 <0.12.10 and >=0.13.0 <0.14.2; Red Hat Streams for Apache Kafka 2 and 3 (all versions). Fixed in Console 0.12.10 and 0.14.2.
Red Hat scored this CVSS 9.9 CRITICAL — the highest non-10 rating possible — and the vector (S:C, C:H/I:H/A:L) reflects a real scope change from namespace to cluster-wide Strimzi resources. However, the vendor score dramatically overstates real-world risk. The prerequisite is not merely 'low privilege' as the vector claims; it requires explicit RBAC assignment of Console CR create, a permission no default OpenShift role grants. The installed base is minuscule (32 GitHub stars, 33 forks), limiting the exposed population to a small subset of Red Hat OpenShift Kafka shops. The escalation itself is bounded to the Strimzi control plane — not full cluster-admin — meaning the attacker reads Kafka user credentials and can disrupt Kafka rebalancing, but cannot create pods in arbitrary namespaces or escalate to node-level access. No public PoC exists, no exploitation has been observed in the wild, and the CVE is not KEV-listed. A reassessment to HIGH 7.5 is appropriate.
4 steps from start to impact.
Obtain Console CR create permission
create (and ideally update/patch) verbs on the consoles.console.streamshub.github.com resource. This is not a default permission in OpenShift or any standard Strimzi deployment — a cluster administrator must explicitly grant it via RBAC.- Kubernetes API access (authenticated)
- RBAC role granting create on Console CRD
- Default OpenShift roles do not include Console CR create
- Most multi-tenant setups restrict CRD manipulation to platform admins
- Requires an explicit, auditable RBAC grant
kubectl auth can-i create consoles.console.streamshub.github.com --list across subjectsSubmit malicious Console CR with attacker-controlled image
Console custom resource, setting spec.containers.api.spec.image to a container image they control (e.g., hosted on a public registry or an internal registry they can push to). The image contains code that reads the ServiceAccount token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token on startup.- Ability to push to a container registry reachable from the cluster
- Console CR create permission (step 1)
- Image pull policies (AlwaysPull + registry allowlists) may block non-approved registries
- Admission webhooks (OPA Gatekeeper, Kyverno) can block non-allowlisted images
- Network egress policies may prevent pulling from external registries
Operator deploys attacker pod with ClusterRole-bound ServiceAccount
- Console-operator is running and healthy
- No admission webhook blocks the image
- If the operator is namespaced with restricted SA, the blast radius is smaller
- Single-tenant deployments are not meaningfully impacted (the attacker already owns the tenant)
Exfiltrate ServiceAccount token and access Strimzi control plane
get/list/watch all KafkaUser resources across all namespaces (exposing SCRAM-SHA-512 credentials), patch Kafka and KafkaRebalance resources to disrupt or reconfigure clusters, and access cluster monitoring data.- Pod is running with the privileged ServiceAccount
- Kubernetes API server is reachable from the pod (always true in-cluster)
- Network policies on console-api pods can restrict API server access (rare but possible)
- TokenRequestProjection with short TTL limits the window
create, update, and patch verbs on consoles.console.streamshub.github.com from all tenant-scoped Roles and ClusterRoles. Only cluster administrators should manage Console CRs. This directly breaks step 1 of the attack path and eliminates the vulnerability entirely without patching. Per the noisgate mitigation SLA for HIGH severity, deploy within 30 days — but given the simplicity of this RBAC change, aim for same-week.spec.containers.api.spec.image does not match your approved registry prefix (e.g., registry.redhat.io/streams-*). This breaks step 2 by preventing arbitrary image injection even if the RBAC prerequisite is met. Deploy within 30 days per noisgate mitigation SLA.- Pod Security Standards (PSS) restricted profile — PSS restricts container capabilities and host access but does NOT restrict which *image* a pod runs. The attacker's image runs as a normal unprivileged container; PSS won't block it.
- Kubernetes RBAC audit logging alone — While audit logs will record the Console CR creation, they do not *prevent* the attack. Detection without prevention is insufficient as a compensating control.
- Container image scanning in CI/CD — The attacker pulls their image at runtime via the operator; your CI/CD pipeline never sees it. Only an admission-time webhook catches this.
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No GreyNoise or Shadowserver tags. No vendor acknowledgment of active exploitation. Disclosed 2026-10-20 (today). |
|---|---|
| Proof-of-concept availability | No public PoC. Not indexed on pocindex.io. No GitHub repositories named after CVE-2026-105312. No ExploitDB or Nuclei template. The attack is conceptually straightforward (create a Console CR with a custom image, read the SA token), so a skilled Kubernetes operator could reproduce it without a published PoC. |
| EPSS score | Not yet scored — CVE was disclosed today (2026-10-20). Expect initial EPSS within 24-48 hours. Given the niche product and no public PoC, predicted EPSS is likely <5th percentile. |
| KEV status | Not listed. No CISA KEV entry as of 2026-10-20. |
| CVSS vector interpretation | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L — Network-reachable (Kubernetes API), low complexity, low privilege (authenticated tenant), no user interaction, scope change (namespace to cluster-wide Strimzi), high confidentiality and integrity impact, low availability impact. The PR:L is generous — the real prerequisite is a specific CRD-level RBAC grant, not merely 'any authenticated user'. |
| Affected version ranges | StreamsHub Console >=0.2.0 <0.12.10 and >=0.13.0 <0.14.2. Red Hat Streams for Apache Kafka 2 (all versions) and 3 (all versions). The console-operator itself has no reported fix version yet. |
| Fixed versions | Console 0.12.10 and 0.14.2. Console-operator fix: not yet released per upstream tracker. Red Hat advisories pending for Streams for Apache Kafka 2.x and 3.x. |
| Installed base / exposure | Extremely niche. The streamshub/console GitHub repo has 32 stars and 33 forks. Deployment requires Red Hat OpenShift with Strimzi and the Console operator installed via OLM. No Shodan/Censys/FOFA exposure data (not internet-facing). Estimated installed base: low hundreds of clusters globally. |
| Disclosure timeline | Disclosed 2026-10-20 via Red Hat security advisory and NVD. Bugzilla tracker: 2510863. Coordinated disclosure through Red Hat product security. |
| Reporting researcher | Not publicly credited in available advisories. Discovered through Red Hat internal or community security review. |
Sources.
Why this verdict
- Prerequisite is stricter than PR:L suggests: The attacker needs explicit RBAC granting
createonconsoles.console.streamshub.github.com— a CRD-level permission that no default OpenShift role includes. This is functionally closer to PR:H in most deployments, which alone would drop the CVSS to ~8.4. - Minuscule installed base compresses population risk: With 32 GitHub stars and deployment limited to Red Hat OpenShift + Strimzi + Console operator, the realistic exposed population is in the low hundreds of clusters globally. This is not a mass-exploitation target.
- Escalation scope is Strimzi control plane, not cluster-admin: The ClusterRole grants read on Strimzi CRDs and patch on Kafka resources — serious for multi-tenant Kafka but the attacker cannot create arbitrary pods, read secrets outside Strimzi CRDs, or escalate to node-level access. The blast radius is bounded.
- Role multiplier: (a) *Low-value role* — dev/test Kafka clusters: the attacker is likely already the sole tenant, no meaningful escalation. (b) *Typical role* — single-tenant production Kafka on OpenShift: Console CR create is restricted to the platform team, so the prerequisite is self-defeating (the admin attacking their own cluster). (c) *High-value role* — multi-tenant production Kafka streaming platform: here the chain succeeds and the blast radius is cross-tenant credential exposure (KafkaUser SCRAM secrets) and Kafka cluster disruption. This is a *tenant-to-tenant* boundary break, not a *tenant-to-cluster-admin* break. The affected component is NOT a canonical high-value-role component (not an IdP, hypervisor, PAM, backup, CA, network edge, or DC role-defining software). It is a niche management UI for Kafka. Even in the worst multi-tenant scenario, the outcome is data-tier credential exposure and Kafka service disruption — not domain takeover or fleet compromise. The HIGH floor is met by the cross-tenant credential exposure; CRITICAL floor is not met because the component is not canonically high-value and the blast radius does not reach fleet/domain/supply-chain scope.
- No exploitation evidence or tooling: No public PoC, no KEV listing, no observed scanning. The CVE is hours old, but the niche target makes weaponization unlikely for mass campaigns.
Why not higher?
CRITICAL would require either a canonical high-value component (this is a niche Kafka UI operator with 32 GitHub stars) or a blast radius reaching domain takeover / fleet compromise / supply-chain pivot. The escalation is bounded to Strimzi CRD read+patch — serious for multi-tenant Kafka, but not full cluster-admin. The prerequisite (Console CR create RBAC) further narrows the realistic attacker pool. There is no exploitation evidence, no PoC, and the CVE is hours old with an extremely small installed base.
Why not lower?
MEDIUM would understate the genuine cross-tenant boundary violation. In organizations that *do* run multi-tenant Strimzi with Console operator, an attacker with Console CR create can read KafkaUser SCRAM credentials across all namespaces and disrupt production Kafka clusters — this is a real confidentiality and integrity impact with a scope change. The attack complexity is genuinely low once the RBAC prerequisite is met, and no user interaction is needed. The HIGH floor is justified by the cross-tenant credential exposure in the high-value deployment role.
Crowdsourced verification payload.
Run this script on any host with kubectl access to the target cluster and permissions to list CRDs, ClusterRoles, and RoleBindings. Example: bash check_cve_2026_105312.sh. No special privileges beyond read access to RBAC and CRD objects are required.
#!/usr/bin/env bash
# check_cve_2026_105312.sh
# Checks whether CVE-2026-105312 affects the current Kubernetes cluster.
# Looks for: (1) StreamsHub Console CRD installed, (2) vulnerable Console operator version,
# (3) tenant Roles granting create on Console CRs.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=VULNERABLE, 1=PATCHED, 2=UNKNOWN
set -euo pipefail
VULN_FOUND=0
CONSOLE_CRD="consoles.console.streamshub.github.com"
echo "=== CVE-2026-105312 Check ==="
echo ""
# Step 1: Check if the Console CRD exists
if ! kubectl get crd "$CONSOLE_CRD" &>/dev/null; then
echo "[INFO] Console CRD ($CONSOLE_CRD) not found on this cluster."
echo "PATCHED"
exit 1
fi
echo "[WARN] Console CRD ($CONSOLE_CRD) is installed."
# Step 2: Check console-operator deployment and image version
CONSOLE_NS=$(kubectl get deployment --all-namespaces -o json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data.get('items', []):
name = item.get('metadata', {}).get('name', '')
if 'console' in name.lower() and 'operator' in name.lower():
print(item['metadata']['namespace'])
break
" 2>/dev/null || true)
if [ -z "$CONSOLE_NS" ]; then
echo "[INFO] No console-operator deployment found. Checking for Console CRs directly..."
else
echo "[INFO] Console operator found in namespace: $CONSOLE_NS"
fi
# Step 3: Check for Console CR instances
CONSOLE_COUNT=$(kubectl get consoles.console.streamshub.github.com --all-namespaces --no-headers 2>/dev/null | wc -l || echo 0)
echo "[INFO] Active Console CRs across cluster: $CONSOLE_COUNT"
# Step 4: Check if any non-admin Roles grant create on Console CRs
echo ""
echo "--- Checking for Roles granting create on Console CRs ---"
ROLE_HITS=$(kubectl get roles --all-namespaces -o json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
hits = []
for item in data.get('items', []):
ns = item.get('metadata', {}).get('namespace', '?')
name = item.get('metadata', {}).get('name', '?')
for rule in item.get('rules', []):
resources = rule.get('resources', [])
verbs = rule.get('verbs', [])
api_groups = rule.get('apiGroups', [])
if ('consoles' in resources or '*' in resources):
if any(v in verbs for v in ['create', 'update', 'patch', '*']):
if any(g in api_groups for g in ['console.streamshub.github.com', '*']):
hits.append(f'{ns}/{name}')
for h in hits:
print(h)
" 2>/dev/null || true)
CLUSTERROLE_HITS=$(kubectl get clusterroles -o json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
hits = []
for item in data.get('items', []):
name = item.get('metadata', {}).get('name', '?')
for rule in item.get('rules', []):
resources = rule.get('resources', [])
verbs = rule.get('verbs', [])
api_groups = rule.get('apiGroups', [])
if ('consoles' in resources or '*' in resources):
if any(v in verbs for v in ['create', 'update', 'patch', '*']):
if any(g in api_groups for g in ['console.streamshub.github.com', '*']):
hits.append(name)
for h in hits:
print(h)
" 2>/dev/null || true)
if [ -n "$ROLE_HITS" ] || [ -n "$CLUSTERROLE_HITS" ]; then
echo "[CRITICAL] Found Roles/ClusterRoles granting Console CR write access:"
[ -n "$ROLE_HITS" ] && echo " Roles: $ROLE_HITS"
[ -n "$CLUSTERROLE_HITS" ] && echo " ClusterRoles: $CLUSTERROLE_HITS"
VULN_FOUND=1
else
echo "[OK] No tenant-accessible Roles grant create/update/patch on Console CRs."
fi
# Step 5: Check console image versions for known vulnerable ranges
echo ""
echo "--- Checking Console pod image versions ---"
kubectl get pods --all-namespaces -o json 2>/dev/null | \
python3 -c "
import sys, json, re
data = json.load(sys.stdin)
for item in data.get('items', []):
ns = item.get('metadata', {}).get('namespace', '?')
name = item.get('metadata', {}).get('name', '?')
for c in item.get('spec', {}).get('containers', []):
img = c.get('image', '')
if 'console' in img.lower() and 'streamshub' in img.lower():
print(f' {ns}/{name}: {img}')
# Try to extract version
m = re.search(r':(\d+\.\d+\.\d+)', img)
if m:
parts = list(map(int, m.group(1).split('.')))
v = parts[0]*10000 + parts[1]*100 + parts[2]
if (v >= 200 and v < 1210) or (v >= 1300 and v < 1402):
print(f' ^^ VULNERABLE version detected')
else:
print(f' ^^ Version appears patched')
" 2>/dev/null || echo " [INFO] No StreamsHub Console pods found."
echo ""
if [ "$VULN_FOUND" -eq 1 ]; then
echo "VULNERABLE"
exit 0
else
echo "PATCHED"
exit 1
fi