# Variant Root Cause Analysis: CVE-2026-52831 Nuclio Cron Trigger Shell Injection

## Summary

No distinct fixed-version bypass or materially different same-root-cause variant was confirmed. The parent vulnerability was reachable because Nuclio's Kubernetes controller converted untrusted cron trigger `event.headers` and `event.body` into a single `/bin/sh -c` string for a generated Kubernetes CronJob. The fixed commit `3356b86a8bfab3f960aa420310ebff765df9dede` removes that shell sink in the centralized CronJob generation path by invoking `curl` directly with discrete argv entries and using `--data-raw` for body content. Bounded variant analysis tested header-key, header-value/body, body-`@`, and header-`@` candidates; all were covered or did not produce command execution on the fixed target.

## Fix Coverage / Assumptions

The original fix relies on this invariant: no attacker-controlled cron trigger header key, header value, or body may be interpreted by a shell while constructing or running the generated Kubernetes CronJob invocator container.

The fix explicitly covers:

- `pkg/platform/kube/functionres/lazy.go`, `(*lazyClient).generateCronTriggerCronJobSpec`.
- The Kubernetes cron-trigger path: `NuclioFunction.spec.triggers[*].kind == "cron"` -> controller reconciliation -> generated Kubernetes `CronJob` -> invocator container.
- Header keys and values, now appended as literal `curl` argv pairs: `"--header", fmt.Sprintf("%s: %s", headerKey, value)`.
- Body values, now appended as `"--data-raw", eventBody` instead of shell-writing to a temp file and using `--data '@/tmp/eventbody.out'`.

The fixed code does not attempt to sanitize or reject metacharacters. Instead, it removes the interpreter that made metacharacters dangerous. That is sufficient for the parent command-injection root cause. One non-shell curl behavior remains in theory (`--header @file`), but the tested Nuclio formatting makes the entire header argument `@/path: value`; curl errors while trying to open that exact string and no OS command executes.

## Variant / Alternate Trigger

Tested candidates and entry points:

1. **Header key with quote/semicolon shell metacharacters**
   - Entry point: Kubernetes/Nuclio API creation or update of a `NuclioFunction` custom resource with a cron trigger.
   - Path: `pkg/platform/kube/functionres/lazy.go:generateCronTriggerCronJobSpec`.
   - Result: parent trigger on vulnerable parent, not a fixed-version bypass. The fixed commit treats the complete header as one argv value after `--header`.

2. **Body with command substitution or shell separators**
   - Entry point: same `NuclioFunction` cron trigger attributes, `event.body`.
   - Path: same function.
   - Result: not a fixed-version bypass. The fixed commit passes the body literally via `--data-raw`; no shell sees `$(id)` or separators.

3. **Body beginning with `@/path` to use curl file-load semantics**
   - Entry point: same cron trigger `event.body`.
   - Path: same function.
   - Result: not a fixed-version bypass. The fixed commit explicitly uses `--data-raw`, covered by the regression test.

4. **Header key beginning with `@/path` to use curl `--header @file` semantics**
   - Entry point: same cron trigger `event.headers` map key.
   - Path: same function and curl argv semantics.
   - Result: not a command-execution bypass. The local probe showed curl fails with `curl_exit=26` while trying to open `/etc/passwd: marker`; no request reaches the server and no marker command executes.

## Impact

- Package/component affected by the parent bug: Nuclio Kubernetes controller, cron-trigger CronJob generation in `pkg/platform/kube/functionres/lazy.go`.
- Affected versions as tested: vulnerable parent `82b9b64ee9bc0c7d99447b5890ef85973fee4e36`; ticket states Nuclio 1.15.27 and earlier are affected.
- Fixed version as tested: `3356b86a8bfab3f960aa420310ebff765df9dede`.
- Risk level and consequences for the parent: critical RCE in the CronJob container context when an actor can create/update a malicious cron-trigger NuclioFunction.
- Variant-stage consequence: no additional fixed-version RCE was reproduced.

## Impact Parity

- Disclosed/claimed maximum impact for the parent: arbitrary OS command execution / RCE from malicious cron trigger headers or body.
- Reproduced impact from this variant run: no fixed-version command execution; the variant script confirmed the vulnerable parent still contains the shell sink and the fixed commit does not, then executed bounded candidate probes.
- Parity: `none` for a new variant/bypass.
- Not demonstrated: no code execution on the fixed commit; no alternate production entry point outside Kubernetes cron-trigger CronJob generation was found.

## Root Cause

The parent root cause was unsafe composition of attacker-controlled cron trigger data into a shell command string stored as `Args: []string{"/bin/sh", "-c", curlCommand}`. The fixed commit is `3356b86a8bfab3f960aa420310ebff765df9dede` (`[Security] Fix cron trigger shell injection (#4139)`). It changes the sink from shell-form execution to exec-form `curl`:

- Vulnerable parent: `headersAsCurlArg` and body shell fragments are concatenated into `curlCommand`, then run through `/bin/sh -c`.
- Fixed commit: `Command: []string{"curl"}` and `Args: curlArgs` ensure untrusted strings are data argv entries.

Because the same underlying bug depends on shell interpretation, and the fixed centralized path removes that interpreter for all cron event headers/body, the same root cause could not be reached by the tested alternate inputs.

## Reproduction Steps

1. Use `bundle/vuln_variant/reproduction_steps.sh`.
2. The script:
   - Resolves the vulnerable parent and fixed commit in the prepared Nuclio repository.
   - Saves patch stats, source excerpts, and a security-policy scan under `bundle/logs/vuln_variant/`.
   - Checks the vulnerable parent for `/bin/sh -c` cron-trigger generation.
   - Checks the fixed commit for `Command: ["curl"]`, no shell-form args, and `--data-raw` body handling.
   - Records a bounded candidate matrix and runs a local curl `--header '@/etc/passwd: marker'` semantics probe.
3. Expected result: exit `1`, meaning no variant reproduced on the fixed version. This is the intended negative result for this run. The script is idempotent and was run twice successfully.

## Evidence

- `bundle/logs/vuln_variant/reproduction_steps.log` — final variant-stage execution log.
- `bundle/logs/vuln_variant/source_identity.txt` — exact resolved commits:
  - vulnerable parent `82b9b64ee9bc0c7d99447b5890ef85973fee4e36`
  - fixed commit `3356b86a8bfab3f960aa420310ebff765df9dede`
- `bundle/logs/vuln_variant/patch_diff.full` — fixed diff showing removal of shell-form `curlCommand` and addition of exec-form `curlArgs`.
- `bundle/logs/vuln_variant/lazy_vulnerable_excerpt.txt` and `bundle/logs/vuln_variant/lazy_fixed_excerpt.txt` — side-by-side relevant source excerpts.
- `bundle/logs/vuln_variant/candidate_matrix.log` — evaluated candidates and rule-out rationale.
- `bundle/logs/vuln_variant/curl_header_at_probe.log` — curl `@` header probe; key excerpt: `curl_exit=26`, no server request, and no command-execution marker.
- `bundle/vuln_variant/runtime_manifest.json` — structured runtime/inspection manifest for the negative validation.

Environment details captured include the repository path, current repository HEAD, fixed commit subject/date, and local curl version.

## Recommendations / Next Steps

- Keep the exec-form `curl` implementation and do not reintroduce `/bin/sh -c` for generated cron-trigger CronJobs.
- Add or keep regression tests covering header values in addition to header keys, body command substitution, and body leading `@` values.
- Consider adding a regression case for header keys beginning with `@` to document curl behavior and prevent future changes from accidentally creating a file-read primitive.
- If future features add new cron-trigger invocation modes, require the same invariant: no shell command string may be built from `event.headers`, `event.body`, trigger names, or function names.

## Additional Notes

- Idempotency confirmation: `bundle/vuln_variant/reproduction_steps.sh` was run twice; both runs completed and exited `1` without crashing, consistently indicating no fixed-version bypass.
- The target repository did not contain a top-level `SECURITY.md` or explicit threat model in the bounded scan. The security boundary used here is the one demonstrated by the parent repro: untrusted function spec data crossing the Kubernetes/Nuclio API boundary into controller-generated Kubernetes workload commands.
- This stage produced a negative verdict, not a claimed new vulnerability.
