# Patch Analysis: CVE-2026-52831 Nuclio Cron Trigger Shell Injection

## Fixed revision and scope

- Repository: `https://github.com/nuclio/nuclio`
- Submitted/vulnerable target tested by repro: parent of fixed commit `3356b86a8bfa`, resolved as `82b9b64ee9bc0c7d99447b5890ef85973fee4e36`.
- Fixed target inspected/tested in this variant stage: `3356b86a8bfab3f960aa420310ebff765df9dede` (`[Security] Fix cron trigger shell injection (#4139)`).
- Patch files:
  - `pkg/platform/kube/functionres/lazy.go`
  - `pkg/platform/kube/functionres/lazy_test.go`

## What the fix changes

The vulnerable function is `(*lazyClient).generateCronTriggerCronJobSpec` in `pkg/platform/kube/functionres/lazy.go`.

Before the fix, the controller generated a single shell command string:

- It concatenated user-controlled `attributes.Event.Headers` into `headersAsCurlArg` using `--header "<key>: <value>"`.
- It concatenated the final curl invocation into `curlCommand`.
- If `attributes.Event.Body` was set, it prepended an `echo <quoted body> > /tmp/eventbody.out && ...` shell fragment and used `--data '@/tmp/eventbody.out'`.
- It put the result into the CronJob pod as `Args: []string{"/bin/sh", "-c", curlCommand}`.

The fixed commit removes the shell string construction and builds an argv vector instead:

- `curlArgs := []string{"--silent"}` initializes discrete curl arguments.
- User header keys are sorted for deterministic output, then appended as `"--header", fmt.Sprintf("%s: %s", headerKey, value)`.
- Default headers are appended as discrete `--header` pairs.
- Body content is optionally JSON-compacted but then appended as `"--data-raw", eventBody`.
- Retry flags and the function address are appended as discrete arguments.
- The CronJob container now uses `Command: []string{"curl"}` and `Args: curlArgs`, not `/bin/sh -c`.

The regression test `TestCronTriggerExecFormNoShellInjection` was added to assert:

- Header keys containing quotes/semicolons are preserved as literal argv values.
- Body strings such as `$(id)` are literal argv values.
- Body values beginning with `@/etc/passwd` are sent via `--data-raw`, not `--data`.
- `/bin/sh` and `-c` do not appear in the generated container args.

## Fix assumptions

The fix assumes the dangerous sink is shell interpretation in the Kubernetes CronJob invocator container. That assumption matches the reproduced root cause: the attacker-controlled NuclioFunction cron trigger crosses the Kubernetes/Nuclio API boundary, the controller generates a CronJob, and the CronJob pod executes the generated container command.

The fixed logic also recognizes one curl-specific post-shell hazard: `--data` treats a leading `@` as a file-load directive. It therefore uses `--data-raw` for body content.

## Code paths covered

The patch is centralized at `generateCronTriggerCronJobSpec`, which is the only Kubernetes CronJob cron-trigger generation point found in source inspection. The call path is:

`NuclioFunction spec.triggers[*].kind == "cron"` -> `createOrUpdateCronJobs` -> `createOrUpdateCronTriggerCronJobs` -> `generateCronTriggerCronJobSpec` -> Kubernetes `CronJob.spec.jobTemplate.spec.template.spec.containers[0]`.

Because all user-controlled cron event header keys, header values, and body content are consumed in this one function, the exec-form conversion covers the parent header-key path and the body command-substitution path.

## Candidate gaps evaluated

1. **Alternate header injection through header values**: fixed by the same argv-vector invariant. Header values are part of a single `--header` value argv; no shell receives them.
2. **Body command substitution or shell separators**: fixed by the same argv-vector invariant and the removal of `echo ... &&` shell prepending.
3. **Body leading `@` file-load behavior**: explicitly fixed by switching from `--data` to `--data-raw`.
4. **Header leading `@` curl file-load behavior**: inspected and probed. Curl treats a `--header` argument that begins with `@` as a header-file directive. Nuclio formats the entire argument as `<key>: <value>`, so a key such as `@/etc/passwd` becomes `@/etc/passwd: marker`; curl attempts to open that exact filename and errors. This is not shell command execution and did not produce a fixed-version RCE marker in the probe.

## Threat model / security policy notes

A repository scan found no top-level `SECURITY.md` or explicit threat-model document. The only security-policy-like repository artifact found within the bounded scan was `.github/workflows/security_scan.yaml`, and the fixed regression test comments explicitly describe the issue as a vulnerability and shell-injection fix. I therefore treated the in-scope boundary as the one demonstrated by the repro: an actor able to create or update a NuclioFunction cron trigger can supply untrusted event headers/body that the controller later turns into a Kubernetes CronJob pod command.

## Behavior before and after the fix

- Vulnerable parent (`82b9b64ee9bc0c7d99447b5890ef85973fee4e36`): generated CronJobs contain `Args: ["/bin/sh", "-c", curlCommand]`; shell metacharacters in header keys or body can execute commands in the CronJob container.
- Fixed commit (`3356b86a8bfab3f960aa420310ebff765df9dede`): generated CronJobs contain `Command: ["curl"]` and discrete `Args`; header/body metacharacters remain curl data and are not evaluated by a shell.

## Conclusion

No patch bypass was confirmed. The fix is complete for the same root cause and trust boundary because it removes the shell sink from the centralized Kubernetes cron-trigger CronJob generation path and covers the known header, body, and `@` body semantics cases with regression tests.
