GHSA-3v79-m2cg-89ww Nuclio: Unsanitized runtimeAttributes.repositories injected into Groovy build.gradle leads to build-time RCE
SummaryNuclio's Java runtime generates a `build.gradle` file during function builds using Go's `text/template` package. The template renders `runtimeAttributes.repositories[]` values with the `{{ . }}` action, which performs no escaping. An attacker can embed a closing brace (`}`) to break out of the `repositories {}` block and append arbitrary Groovy statements that execute unconditionally during the Gradle configuration phase.The Dashboard API runs with NOP authentication by default, so no credentials are required. The build container runs as root. The injected command output confirmed by dynamic testing:**CWE**: CWE-94 (Improper Control of Generation of Code / Code Injection)**Affected versions**: Nuclio <= 1.15.27 (latest as of 2026-05-17, dynamically verified)DetailsRoot Cause`pkg/processor/build/runtime/java/runtime.go` — function `createGradleBuildScript()`**Step 1. User input flows from the API into the template data map without validation**`types.go:50-64` — `newBuildAttributes()` decodes `runtimeAttributes` with no content inspection. Any string is accepted for each element of `Repositories`:**Step 2. `text/template` renders repositories verbatim into Groovy DSL**`runtime.go:111,139` — the template is parsed with `text/template`, which does not HTML-encode or escape special characters. `{{ . }}` emits each repository string as-is:The template section for repositories (`runtime.go:155-159`):`{{ . }}` is the verbatim output action. Because `text/template` (unlike `html/template`) applies no contextual escaping, any character — including `}`, `(`, `)`, newlines — is written directly to the `.gradle` file.**Step 3. Gradle evaluates the injected Groovy at configuration phase**The generated `build.gradle` is passed to `./build-user-handler.sh` inside the `quay.io/nuclio/handler-builder-java-onbuild` container. That script runs:Groovy evaluates every top-level statement in `build.gradle` before executing any task. Injected code therefore runs unconditionally on both invocations.Injection MechanicsPayload for `repositories[0]`:Generated `build.gradle` (confirmed by Dashboard DEBUG log at path `/tmp/nuclio-build-378373988/staging/handler/build.gradle`):The `}` on line 9 closes the `repositories {}` block. `println(...)` on line 10 becomes a top-level Groovy statement. `repositories {` on line 11 re-opens a new block that the template's trailing `}` correctly closes, making the entire file syntactically valid.Groovy's `List.execute()` extension method (e.g., `['sh', '-c', 'cmd'].execute()`) runs an OS process. `.text` captures its standard output. The injected `println` logs the output to Gradle's stdout, which appears in the kaniko executor log.Proof of ConceptEnvironment SetupThe following steps reproduce the verified environment. All commands were executed and verified on 2026-05-17.1. Create a dedicated kind clusterExpected output:2. Pre-load required images3. Deploy a local image registry accessible from kind nodes4. Install Nuclio via HelmExpected output:5. Expose the Dashboard and verify connectivityExploitation StepsStep 1 — Send the malicious function definitionThe `runtimeAttributes.repositories` field accepts any string. Use Python to build a
correctly escaped JSON payload:Expected output:No authentication required. No validation error for the injected repository value.Step 2 — Confirm template injection in the Dashboard DEBUG logActual output (from verified run):The Dashboard DEBUG log (path logged: `/tmp/nuclio-build-378373988/staging/handler/build.gradle`) confirms the injected Groovy reached the file verbatim.Step 3 — Wait for the kaniko build job and observe RCE outputActual output (from verified run, pod `nuclio-kanikojob.nuclioprocessorvul006rcev3latest.tkxsslz06ppcr`):The marker `[RCE-PROOF]` appears twice — once per `gradle` invocation (`gradle tasks`
and `gradle userHandler`). The output confirms:`uid=0(root)` — execution as root inside the builder containerThe pod name as hostname — confirms execution is inside the real build container, not simulated`root` — `whoami` output corroborates the UIDCleanupImpactDirect ImpactAn unauthenticated attacker can execute arbitrary OS commands as root inside the function builder container on every Java function build. Confirmed capabilities from the build container environment:Read/write the build container filesystemAccess network endpoints reachable from the build podTamper with the compiled function artifact (`.jar`) before it is packaged into the
processor image — effectively poisoning the resulting function's imagePrivilege Escalation — Docker Socket Escape (Verified: NOT directly exploitable in default configuration)**Verification result: In the default docker builder configuration, direct Docker socket escape via Gradle code injection is NOT exploitable.**EnvironmentCluster: kind-vul-009, Nuclio v1.15.27-amd64Builder: `NUCLIO_CONTAINER_BUILDER_KIND=docker` (confirmed via `kubectl describe`)Dashboard pod: `nuclio-dashboard-5f8ddc949c-sfzh4`Verified: 2026-05-19 06:21 UTCdocker.sock Mount Confirmed on Dashboard PodThe Docker socket is accessible within the Dashboard container itself (Docker v29.1.2 API confirmed reachable).Build Flow in docker Builder ModeNuclio generates a `Dockerfile.onbuild` and submits it to Docker daemon via the socket:Actual command issued (from Dashboard DEBUG log):Probe Results (Step 7/9 RUN Layer)Injection payload in `repositories[0]`:Gradle output (captured twice — once per `gradle tasks` / `gradle userHandler` invocation):The RCE executed successfully. The `docker.sock` does not exist inside the `RUN`-stage container.Root CauseEach `RUN` instruction in a `docker build` executes inside an isolated intermediate container
(`b747a20b21ba`). That container:Has a filesystem built from image layers only — it does **not** inherit volume mounts from the caller (the Dashboard container).`--network host` shares the host network namespace (explaining `hostname=VM-0-8-ubuntu`) but does **not** share the filesystem.Docker daemon never exposes the host filesystem (including `/var/run/docker.sock`) to build-stage containers unless the Dockerfile explicitly arranges it.Conditions Required for ExploitabilityThis path becomes exploitable only under non-default configurations:**Dockerfile with explicit socket bind**: e.g., BuildKit `--mount=type=bind,source=/var/run/docker.sock,...` in the onbuild image, or replacing `docker build` with `docker run -v /var/run/docker.sock:/var/run/docker.sock`**Privileged build containers**: `--privileged` mode with `mknod` device node creation**Docker-in-Docker setup**: Docker daemon pre-installed and launched inside the builder imageNone of these conditions exist in the standard Nuclio Helm chart deployment.Evidence: `evidence/logs/docker-builder-socket-probe.log`Privilege Escalation — Kubernetes ServiceAccount TokenThe build pod can read the ServiceAccount token mounted within it. **However**, the kaniko Job's `serviceAccountName` is sourced from `builderServiceAccount`, function `serviceAccount`, `kaniko.defaultServiceAccount`, or the platform's default function SA (see `pkg/containerimagebuilderpusher/kaniko.go:301`, `:375`, `:840-849`). This is not inherently the same as the Nuclio Dashboard's high-privilege ServiceAccount.In deployments where the build pod uses a high-privilege ServiceAccount (e.g., where an administrator has bound overly broad RBAC roles to the builder SA), an attacker can read the token and query the Kubernetes API:The effective permissions of this token depend on the RBAC bindings of the build pod's ServiceAccount. Under least-privilege configurations, this token may not be able to access sensitive resources.Cross-Tenant Access (Horizontal Escalation)Nuclio uses Kubernetes namespaces for tenant isolation. Build containers in docker mode share the host Docker daemon. An attacker can enumerate and access containers belonging to other tenants via the Docker socket.Cloud Instance Metadata (SSRF — Managed Kubernetes)In EKS, GKE, or AKS environments, the build container can reach the cloud instance metadata service:Obtained temporary IAM credentials grant access to AWS services (ECR, S3, etc.) available to the node's IAM role.Severity**CVSS 3.1 Score: 10.0 (Critical)**| Metric | Value | Rationale |
|--------|-------|-----------|
| Attack Vector | Network | Dashboard API is network-accessible |
| Attack Complexity | Low | Single POST request; no race condition or special preparation |
| Privileges Required | None | Default NOP authentication requires no credentials |
| User Interaction | None | No user action required |
| Scope | Changed | Impact can escape the build container under common production deployments (see below) |
| Confidentiality | High | Registry credentials, SA tokens, cloud credentials readable in most deployments |
| Integrity | High | Function images can be tampered; cluster resources modifiable |
| Availability | High | Build pipeline can be disrupted; cluster resources deletable |Rating RationaleThis RCE has realistic conditions for further credential acquisition and lateral movement from the build container. In particular, under the following common production deployment scenarios:Kaniko builds use registry secrets (image push credentials mounted into the build pod)ECR registry provider secrets are configuredNode IAM metadata is reachable (IMDS not blocked)Build pods use a high-privilege ServiceAccountAn attacker can read image registry credentials, AWS/GCP temporary credentials, or Kubernetes SA tokens, and subsequently poison the image registry, access cluster resources, or pivot to cloud resources. A **Critical** rating is justified under these common deployment conditions.**Downgrade conditions**: If a deployment follows least-privilege principles — no registry/cloud credential mounts, IMDS blocked, build SA has no sensitive RBAC bindings — the impact is primarily limited to code execution within the build container and artifact tampering. This remains **High** severity but should not be justified on the basis of "default lateral movement."Affected VersionsNuclio <= **1.15.27** (latest release as of 2026-05-17)All versions that include the Java runtime build path
(`pkg/processor/build/runtime/java/runtime.go`)The vulnerability was introduced when the Java runtime and its `runtimeAttributes` support were added and has not been addressed in any release to date.Patched Versionshttps://github.com/nuclio/nuclio/releases/tag/1.16.5WorkaroundsUntil a patch is released, the following mitigations reduce exposure:**Enable authentication on the Dashboard.** Set `NUCLIO_AUTH_KIND` to a non-NOP
authenticator (e.g., `iguazio`). This prevents unauthenticated access to the function
creation API.**Network-restrict the Dashboard port (8070).** Allow access only from trusted internal
networks or VPN. Do not expose the Dashboard to the public internet.**Disable Java runtime support** if not in use. Remove the Java runtime handler from
the dashboard deployment configuration.**Use kaniko over docker builder.** In kaniko mode the Docker socket is not mounted,
eliminating the host-escape path. The build-time RCE remains exploitable, but the
blast radius is reduced to the build pod.Remediation Recommendations**Option 1 — Input validation (recommended for quick fix)**In `newBuildAttributes()` (`types.go:50`), validate each repository string against an allowlist pattern before accepting it:**Option 2 — Replace `text/template` with a safe rendering approach**The repositories block should not use a Go template at all. Build the `build.gradle` content programmatically using string concatenation with per-value validation, rather than via a template that cannot express per-field escaping semantics.**Option 3 — Content Security: reject newlines and Groovy metacharacters**Reject any repository value containing `\n`, `\r`, `{`, `}`, `(`, `)`, `'`, `"`. These characters are not present in valid Maven repository declarations.Resources`pkg/processor/build/runtime/java/runtime.go` — `createGradleBuildScript()` (line 87)`pkg/processor/build/runtime/java/runtime.go` — `getGradleBuildScriptTemplateContents()` (line 149)`pkg/processor/build/runtime/java/types.go` — `newBuildAttributes()` (line 50)Go `text/template` documentation: https://pkg.go.dev/text/templateGroovy `List.execute()` / `String.execute()`: https://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/String.html#execute()Nuclio Dashboard authentication configuration: https://nuclio.io/docs/latest/reference/api/nuclio_dashboard_api/
CVSS v3.1 score: 8