VYPR
High severityNVD Advisory· Published Mar 6, 2026· Updated Mar 9, 2026

Nuclio Shell Runtime Command Injection Leading to Privilege Escalation

CVE-2026-29042

Description

Nuclio is a "Serverless" framework for Real-Time Events and Data Processing. Prior to version 1.15.20, the Nuclio Shell Runtime component contains a command injection vulnerability in how it processes user-supplied arguments. When a function is invoked via HTTP, the runtime reads the X-Nuclio-Arguments header and directly incorporates its value into shell commands without any validation or sanitization. This issue has been patched in version 1.15.20.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Nuclio Shell Runtime processes the X-Nuclio-Arguments HTTP header without sanitization, allowing attackers to inject OS commands and achieve cluster-level compromise.

Vulnerability Description Nuclio's Shell Runtime component is vulnerable to OS command injection (CWE-78) due to improper handling of the X-Nuclio-Arguments HTTP header. When a function is invoked, the runtime reads this header and directly concatenates its value into a shell command executed via sh -c without any validation or escaping [1][2]. The vulnerable code in runtime.go splits the header by spaces and passes the resulting strings directly to exec.CommandContext, allowing shell metacharacters like ;, |, $(), or backticks to be interpreted.

Exploitation Prerequisites An attacker only needs network access to invoke a Nuclio Shell function via HTTP. No additional authentication is required beyond function invocation permissions. By crafting a malicious X-Nuclio-Arguments header containing shell metacharacters, the attacker can inject arbitrary commands that execute within the function container [1]. Since Nuclio functions typically run with root privileges, the injected commands run with full container-level permissions.

Impact Successful exploitation enables arbitrary command execution, which can be leveraged to exfiltrate sensitive data such as the container's service account token. With the token, an attacker can access the Kubernetes API using the function's service account, which may have cluster-admin privileges, leading to complete cluster compromise [1]. The reference advisory rates this as critical due to the potential for privilege escalation to cluster-wide control.

Mitigation The vulnerability has been fixed in Nuclio version 1.15.20, released on 2026-03-06. Users should upgrade immediately. The commit [4] includes validation and sanitization for the X-Nuclio-Arguments header. No workarounds are available, as the header processing is core to the Shell Runtime functionality.

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/nuclio/nuclioGo
< 1.15.201.15.20

Affected products

2
  • Nuclio/Nucliollm-create
    Range: <1.15.20
  • nuclio/nucliov5
    Range: < 1.15.20

Patches

1
5352d7e16cf9

[Security] Fix CVE-2026-29042 - OS Command Injection in shell runtime (#4030)

https://github.com/nuclio/nuclioKaterina MolchanovaMar 4, 2026via ghsa
3 files changed · +264 2
  • pkg/processor/runtime/shell/runtime.go+8 2 modified
    @@ -203,8 +203,10 @@ func (s *shell) processEvent(context context.Context,
     
     	if s.commandInPath {
     
    -		// if the command is an executable, run it as a command with sh -c.
    -		cmd = exec.CommandContext(context, "sh", "-c", strings.Join(command, " "))
    +		// Run the command directly without sh -c to prevent shell metacharacter
    +		// interpretation in user-supplied arguments (CVE-2026-29042).
    +		// Go's exec resolves the command via LookPath internally.
    +		cmd = exec.CommandContext(context, command[0], command[1:]...)
     	} else {
     
     		// if the command is a shell script run it with sh(without -c). this will make sh
    @@ -293,6 +295,10 @@ func (s *shell) getCommandArguments(event nuclio.Event) []string {
     		arguments = s.configuration.Arguments
     	}
     
    +	if arguments == "" {
    +		return nil
    +	}
    +
     	return strings.Split(arguments, " ")
     }
     
    
  • pkg/processor/runtime/shell/runtime_test.go+174 0 modified
    @@ -19,13 +19,15 @@ limitations under the License.
     package shell
     
     import (
    +	"fmt"
     	"net/http"
     	"os"
     	"path"
     	"testing"
     	"time"
     
     	"github.com/nuclio/nuclio/pkg/common"
    +	"github.com/nuclio/nuclio/pkg/common/headers"
     	"github.com/nuclio/nuclio/pkg/functionconfig"
     	"github.com/nuclio/nuclio/pkg/platformconfig"
     	"github.com/nuclio/nuclio/pkg/processor"
    @@ -44,6 +46,33 @@ func (ti *TestTriggerInfoProvider) GetClass() string { return "test class" }
     func (ti *TestTriggerInfoProvider) GetKind() string  { return "test kind" }
     func (ti *TestTriggerInfoProvider) GetName() string  { return "test name" }
     
    +// testEventWithHeaders extends MemoryEvent with working GetHeaderByteSlice and
    +// GetHeaderString implementations. MemoryEvent.GetHeader reads from the Headers
    +// map, but the AbstractEvent base's GetHeaderByteSlice always returns empty,
    +// so GetHeaderString (which calls GetHeaderByteSlice) never sees the map values.
    +type testEventWithHeaders struct {
    +	nuclio.MemoryEvent
    +}
    +
    +func (event *testEventWithHeaders) GetHeaderByteSlice(key string) []byte {
    +	header := event.GetHeader(key)
    +	if header == nil {
    +		return nil
    +	}
    +	switch typedValue := header.(type) {
    +	case string:
    +		return []byte(typedValue)
    +	case []byte:
    +		return typedValue
    +	default:
    +		return nil
    +	}
    +}
    +
    +func (event *testEventWithHeaders) GetHeaderString(key string) string {
    +	return string(event.GetHeaderByteSlice(key))
    +}
    +
     type ShellRuntimeSuite struct {
     	suite.Suite
     
    @@ -135,3 +164,148 @@ func TestShellRuntimeSuite(t *testing.T) {
     	}
     	suite.Run(t, new(ShellRuntimeSuite))
     }
    +
    +// CommandInjectionTestSuite demonstrates CVE-2026-29042 (OS Command Injection) in the shell
    +// runtime's X-Nuclio-Arguments header processing. When the handler resolves to a
    +// command in PATH, the runtime uses `sh -c` to execute it,  joining
    +// the command and user-supplied arguments into a single string without sanitization
    +// This allows shell metacharacters in the header value to break
    +// out of the intended command and execute arbitrary commands with the container's
    +// privileges.
    +type CommandInjectionTestSuite struct {
    +	suite.Suite
    +	runtimeInstance       runtime.Runtime
    +	logger                logger.Logger
    +	tempRuntimeHandlerDir string
    +}
    +
    +func (suite *CommandInjectionTestSuite) SetupSuite() {
    +	suite.logger, _ = nucliozap.NewNuclioZapTest("test")
    +	configuration, err := NewConfiguration(suite.resolveRuntimeConfiguration())
    +	suite.Require().NoError(err, "Failed to create configuration")
    +
    +	// Point handler dir to a directory where 'true' does not exist as a file,
    +	// so the runtime resolves it via PATH and sets commandInPath=true.
    +	// 'true' is ideal because it produces no output and ignores all arguments,
    +	// so any injected command output (e.g. "INJECTED") can only appear if
    +	// shell metacharacters were actually interpreted.
    +	suite.tempRuntimeHandlerDir = os.Getenv("NUCLIO_SHELL_HANDLER_DIR")
    +	err = os.Setenv("NUCLIO_SHELL_HANDLER_DIR", os.TempDir())
    +	suite.Require().NoError(err, "Failed to set NUCLIO_SHELL_HANDLER_DIR")
    +
    +	configuration.Spec.Handler = "true:main"
    +
    +	suite.runtimeInstance, err = NewRuntime(suite.logger, configuration)
    +	suite.Require().NoError(err, "Failed to create shell runtime")
    +}
    +
    +func (suite *CommandInjectionTestSuite) TearDownSuite() {
    +	suite.Require().NoError(os.Setenv("NUCLIO_SHELL_HANDLER_DIR", suite.tempRuntimeHandlerDir))
    +}
    +
    +func (suite *CommandInjectionTestSuite) TestShellMetacharactersAreNotInterpreted() {
    +	testCases := []struct {
    +		name                string
    +		argumentsPayload    string
    +		forbiddenInResponse string
    +	}{
    +		{
    +			name:                "semicolon must not break out of command",
    +			argumentsPayload:    "; echo INJECTED ;",
    +			forbiddenInResponse: "INJECTED",
    +		},
    +		{
    +			name:                "backticks must not perform command substitution",
    +			argumentsPayload:    "`echo INJECTED`",
    +			forbiddenInResponse: "INJECTED",
    +		},
    +		{
    +			name:                "dollar-paren must not perform command substitution",
    +			argumentsPayload:    "$(echo INJECTED)",
    +			forbiddenInResponse: "INJECTED",
    +		},
    +		{
    +			name:                "pipe must not redirect to another command",
    +			argumentsPayload:    "| echo INJECTED",
    +			forbiddenInResponse: "INJECTED",
    +		},
    +		{
    +			name:                "double-ampersand must not chain commands",
    +			argumentsPayload:    "&& echo INJECTED",
    +			forbiddenInResponse: "INJECTED",
    +		},
    +		{
    +			name:                "semicolon must not allow reading sensitive files",
    +			argumentsPayload:    "; cat /etc/passwd ;",
    +			forbiddenInResponse: "root:",
    +		},
    +	}
    +
    +	for _, testCase := range testCases {
    +		suite.Run(testCase.name, func() {
    +			responseBody := suite.getResponseBodyForArguments(testCase.argumentsPayload)
    +			suite.Require().NotContains(responseBody, testCase.forbiddenInResponse)
    +		})
    +	}
    +}
    +
    +func (suite *CommandInjectionTestSuite) TestServiceAccountTokenCannotBeExfiltratedViaInjection() {
    +
    +	// Simulates the attack described in the vulnerability report: reading a
    +	// Kubernetes ServiceAccount token from the filesystem via command injection.
    +	// A temp file stands in for /var/run/secrets/kubernetes.io/serviceaccount/token.
    +	fakeTokenContent := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.fake-sa-token"
    +	tempFile, err := os.CreateTemp("", "fake-sa-token-*")
    +	suite.Require().NoError(err)
    +	defer os.Remove(tempFile.Name())
    +
    +	_, err = tempFile.WriteString(fakeTokenContent)
    +	suite.Require().NoError(err)
    +	suite.Require().NoError(tempFile.Close())
    +
    +	responseBody := suite.getResponseBodyForArguments(fmt.Sprintf("; cat %s ;", tempFile.Name()))
    +	suite.Require().NotContains(responseBody, fakeTokenContent,
    +		"Command injection must not allow reading ServiceAccount tokens from the filesystem")
    +}
    +
    +// getResponseBodyForArguments sends an event with the given X-Nuclio-Arguments header
    +// and returns the response body as a string. Returns empty string if the runtime
    +// returns an error or nil response, since either outcome means no injection occurred.
    +func (suite *CommandInjectionTestSuite) getResponseBodyForArguments(arguments string) string {
    +	event := &testEventWithHeaders{
    +		MemoryEvent: nuclio.MemoryEvent{
    +			Body: []byte("test"),
    +			Headers: map[string]interface{}{
    +				headers.Arguments: arguments,
    +			},
    +		},
    +	}
    +	event.SetTriggerInfoProvider(&TestTriggerInfoProvider{})
    +
    +	response, err := suite.runtimeInstance.ProcessEvent(event, suite.logger)
    +	if err != nil || response == nil {
    +		return ""
    +	}
    +
    +	return string(response.(nuclio.Response).Body)
    +}
    +
    +func (suite *CommandInjectionTestSuite) resolveRuntimeConfiguration() *runtime.Configuration {
    +	return &runtime.Configuration{
    +		FunctionLogger: suite.logger,
    +		Configuration: &processor.Configuration{
    +			Config: functionconfig.Config{
    +				Meta: functionconfig.Meta{},
    +				Spec: functionconfig.Spec{},
    +			},
    +			PlatformConfig: &platformconfig.Config{},
    +		},
    +	}
    +}
    +
    +func TestCommandInjectionTestSuite(t *testing.T) {
    +	if testing.Short() {
    +		return
    +	}
    +	suite.Run(t, new(CommandInjectionTestSuite))
    +}
    
  • pkg/processor/runtime/shell/test/shell_test.go+82 0 modified
    @@ -123,6 +123,88 @@ func (suite *TestSuite) TestStress() {
     	suite.BlastHTTP(blastConfiguration)
     }
     
    +// TestCommandInjectionIsBlocked verifies that shell metacharacters in the
    +// X-Nuclio-Arguments header are not interpreted when the handler is a PATH
    +// command (commandInPath=true). Uses 'true' which produces no output and
    +// ignores all arguments, so any injected command output can only appear if
    +// shell metacharacters were actually interpreted (CVE-2026-29042).
    +func (suite *TestSuite) TestCommandInjectionIsBlocked() {
    +	statusOK := http.StatusOK
    +
    +	// Deploy 'true' as a PATH-based handler (no source code needed).
    +	createFunctionOptions := suite.GetDeployOptions("cmd-injection-test", "/dev/null")
    +	createFunctionOptions.FunctionConfig.Spec.Handler = "true"
    +
    +	suite.DeployFunctionAndRequests(createFunctionOptions, []*httpsuite.Request{
    +		{
    +			Name:        "semicolon must not break out of command",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "; echo INJECTED ;",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "INJECTED")
    +			},
    +		},
    +		{
    +			Name:        "backticks must not perform command substitution",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "`echo INJECTED`",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "INJECTED")
    +			},
    +		},
    +		{
    +			Name:        "dollar-paren must not perform command substitution",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "$(echo INJECTED)",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "INJECTED")
    +			},
    +		},
    +		{
    +			Name:        "pipe must not redirect to another command",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "| echo INJECTED",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "INJECTED")
    +			},
    +		},
    +		{
    +			Name:        "double-ampersand must not chain commands",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "&& echo INJECTED",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "INJECTED")
    +			},
    +		},
    +		{
    +			Name:        "semicolon must not allow reading sensitive files",
    +			RequestBody: "test",
    +			RequestHeaders: map[string]interface{}{
    +				headers.Arguments: "; cat /etc/passwd ;",
    +			},
    +			ExpectedResponseStatusCode: &statusOK,
    +			ExpectedResponseBody: func(body []byte) {
    +				suite.Require().NotContains(string(body), "root:")
    +			},
    +		},
    +	})
    +}
    +
     func TestIntegrationSuite(t *testing.T) {
     	if testing.Short() {
     		return
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.